@principles/install-layout 0.1.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.
@@ -0,0 +1,48 @@
1
+ export declare const INSTALL_LAYOUT_VERSION: 1;
2
+ export declare const INSTALL_LAYOUT_MODES: readonly ["canonical", "legacy"];
3
+ export type InstallLayoutMode = (typeof INSTALL_LAYOUT_MODES)[number];
4
+ export declare const INSTALL_HOSTS: readonly ["codex", "openclaw"];
5
+ export type InstallHost = (typeof INSTALL_HOSTS)[number];
6
+ export interface InstallManifest {
7
+ layoutVersion: typeof INSTALL_LAYOUT_VERSION;
8
+ mode: InstallLayoutMode;
9
+ hosts: InstallHost[];
10
+ }
11
+ export interface InstallLayoutPaths {
12
+ homeDir: string;
13
+ pdDir: string;
14
+ manifest: string;
15
+ runtimeDir: string;
16
+ pluginDir: string;
17
+ installLayoutDir: string;
18
+ coreDir: string;
19
+ hostRuntimeDir: string;
20
+ pdCliDir: string;
21
+ consoleDir: string;
22
+ binDir: string;
23
+ codexDir: string;
24
+ openClawDir: string;
25
+ openClawExtensionDir: string;
26
+ }
27
+ export interface LayoutResolution {
28
+ mode: InstallLayoutMode | 'missing';
29
+ paths: InstallLayoutPaths;
30
+ manifest?: InstallManifest;
31
+ reason?: string;
32
+ nextAction?: string;
33
+ }
34
+ export interface ResolveInstallLayoutOptions {
35
+ homeDir: string;
36
+ manifest?: unknown;
37
+ canonicalRuntimeExists: boolean;
38
+ legacyExtensionExists: boolean;
39
+ }
40
+ export declare function parseInstallManifest(value: unknown): {
41
+ manifest?: InstallManifest;
42
+ error?: string;
43
+ };
44
+ export declare function getInstallLayoutPaths(homeDir: string): InstallLayoutPaths;
45
+ export declare function resolveInstallLayout(options: ResolveInstallLayoutOptions): LayoutResolution;
46
+ export declare function getConsoleServerEntry(paths: InstallLayoutPaths, mode: InstallLayoutMode): string;
47
+ export declare function getConsoleWebIndex(paths: InstallLayoutPaths, mode: InstallLayoutMode): string;
48
+ export declare function getPdCliEntry(paths: InstallLayoutPaths, mode: InstallLayoutMode): string;
package/dist/index.js ADDED
@@ -0,0 +1,81 @@
1
+ import * as path from 'node:path';
2
+ export const INSTALL_LAYOUT_VERSION = 1;
3
+ export const INSTALL_LAYOUT_MODES = ['canonical', 'legacy'];
4
+ export const INSTALL_HOSTS = ['codex', 'openclaw'];
5
+ function isRecord(value) {
6
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
7
+ }
8
+ function isHost(value) {
9
+ return typeof value === 'string' && INSTALL_HOSTS.includes(value);
10
+ }
11
+ export function parseInstallManifest(value) {
12
+ if (!isRecord(value))
13
+ return { error: 'install_manifest_malformed: expected an object' };
14
+ if (value.layoutVersion !== INSTALL_LAYOUT_VERSION) {
15
+ return { error: `install_manifest_unsupported_version: ${String(value.layoutVersion)}` };
16
+ }
17
+ if (value.mode !== 'canonical' && value.mode !== 'legacy') {
18
+ return { error: 'install_manifest_malformed: mode must be canonical or legacy' };
19
+ }
20
+ if (!Array.isArray(value.hosts) || value.hosts.length === 0 || !value.hosts.every(isHost)) {
21
+ return { error: 'install_manifest_malformed: hosts must contain codex and/or openclaw' };
22
+ }
23
+ const hosts = [...new Set(value.hosts)];
24
+ return { manifest: { layoutVersion: INSTALL_LAYOUT_VERSION, mode: value.mode, hosts } };
25
+ }
26
+ export function getInstallLayoutPaths(homeDir) {
27
+ const home = path.resolve(homeDir);
28
+ const pdDir = path.join(home, '.pd');
29
+ const runtimeDir = path.join(pdDir, 'runtime');
30
+ const openClawDir = path.join(home, '.openclaw');
31
+ return {
32
+ homeDir: home,
33
+ pdDir,
34
+ manifest: path.join(pdDir, 'install.json'),
35
+ runtimeDir,
36
+ pluginDir: path.join(runtimeDir, 'plugin'),
37
+ installLayoutDir: path.join(runtimeDir, 'install-layout'),
38
+ coreDir: path.join(runtimeDir, 'core'),
39
+ hostRuntimeDir: path.join(runtimeDir, 'host-runtime'),
40
+ pdCliDir: path.join(runtimeDir, 'pd-cli'),
41
+ consoleDir: path.join(runtimeDir, 'console'),
42
+ binDir: path.join(runtimeDir, 'bin'),
43
+ codexDir: path.join(pdDir, 'codex'),
44
+ openClawDir,
45
+ openClawExtensionDir: path.join(openClawDir, 'extensions', 'principles-disciple'),
46
+ };
47
+ }
48
+ export function resolveInstallLayout(options) {
49
+ const paths = getInstallLayoutPaths(options.homeDir);
50
+ const parsed = parseInstallManifest(options.manifest);
51
+ if (parsed.manifest && parsed.manifest.mode === 'canonical' && options.canonicalRuntimeExists) {
52
+ return { mode: 'canonical', paths, manifest: parsed.manifest };
53
+ }
54
+ if (options.legacyExtensionExists) {
55
+ return {
56
+ mode: 'legacy',
57
+ paths,
58
+ ...(parsed.manifest ? { manifest: parsed.manifest } : {}),
59
+ reason: parsed.error ?? 'install_layout_legacy_fallback',
60
+ nextAction: 'Run the installer update to migrate the shared runtime to ~/.pd/runtime.',
61
+ };
62
+ }
63
+ return {
64
+ mode: 'missing',
65
+ paths,
66
+ reason: parsed.error ?? 'install_runtime_missing',
67
+ nextAction: 'Run npx create-principles-disciple to install or repair PD.',
68
+ };
69
+ }
70
+ export function getConsoleServerEntry(paths, mode) {
71
+ const root = mode === 'canonical' ? paths.consoleDir : path.join(paths.openClawExtensionDir, 'console');
72
+ return path.join(root, 'dist', 'server.js');
73
+ }
74
+ export function getConsoleWebIndex(paths, mode) {
75
+ const root = mode === 'canonical' ? paths.consoleDir : path.join(paths.openClawExtensionDir, 'console');
76
+ return path.join(root, 'dist', 'web', 'index.html');
77
+ }
78
+ export function getPdCliEntry(paths, mode) {
79
+ const root = mode === 'canonical' ? paths.pdCliDir : path.join(paths.openClawExtensionDir, 'pd-cli');
80
+ return path.join(root, 'dist', 'index.js');
81
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getInstallLayoutPaths, getConsoleServerEntry, parseInstallManifest, resolveInstallLayout } from './index.js';
3
+ describe('install layout', () => {
4
+ it('resolves a canonical Codex-only install without OpenClaw', () => {
5
+ const paths = getInstallLayoutPaths('C:/Users/alice');
6
+ const result = resolveInstallLayout({
7
+ homeDir: 'C:/Users/alice',
8
+ manifest: { layoutVersion: 1, mode: 'canonical', hosts: ['codex'] },
9
+ canonicalRuntimeExists: true,
10
+ legacyExtensionExists: false,
11
+ });
12
+ expect(result.mode).toBe('canonical');
13
+ expect(result.manifest?.hosts).toEqual(['codex']);
14
+ expect(paths.openClawExtensionDir).toContain('.openclaw');
15
+ expect(paths.pluginDir).toContain('plugin');
16
+ expect(paths.installLayoutDir).toContain('install-layout');
17
+ expect(getConsoleServerEntry(result.paths, 'canonical')).toContain('.pd');
18
+ });
19
+ it('falls back to legacy only when the old extension exists', () => {
20
+ const result = resolveInstallLayout({
21
+ homeDir: '/home/alice',
22
+ manifest: undefined,
23
+ canonicalRuntimeExists: false,
24
+ legacyExtensionExists: true,
25
+ });
26
+ expect(result.mode).toBe('legacy');
27
+ expect(result.nextAction).toContain('migrate');
28
+ });
29
+ it('rejects malformed manifests instead of silently treating them as canonical', () => {
30
+ expect(parseInstallManifest({ layoutVersion: 1, mode: 'canonical', hosts: ['wat'] })).toEqual({
31
+ error: 'install_manifest_malformed: hosts must contain codex and/or openclaw',
32
+ });
33
+ });
34
+ });
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@principles/install-layout",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": ["dist"],
14
+ "license": "MIT",
15
+ "publishConfig": { "access": "public" },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "test": "npm run build && vitest run"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^26.1.2",
22
+ "typescript": "^7.0.2",
23
+ "vitest": "^4.1.10"
24
+ }
25
+ }