@gaia-ai/core 0.5.4 → 0.6.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.
Files changed (34) hide show
  1. package/dist/src/cli/commands.d.ts +27 -0
  2. package/dist/src/cli/commands.js +1 -0
  3. package/dist/src/cli/gaia-dir.d.ts +51 -0
  4. package/dist/src/cli/gaia-dir.js +152 -0
  5. package/dist/src/cli/load-gaia-config.d.ts +25 -0
  6. package/dist/src/cli/load-gaia-config.js +110 -0
  7. package/dist/src/cli/machine-context.d.ts +24 -0
  8. package/dist/src/cli/machine-context.js +45 -0
  9. package/dist/src/cli/paths.d.ts +11 -0
  10. package/dist/src/cli/paths.js +31 -0
  11. package/dist/src/cli/resolve-module.d.ts +6 -0
  12. package/dist/src/cli/resolve-module.js +24 -0
  13. package/dist/src/conductor-registry/index.d.ts +23 -0
  14. package/dist/src/conductor-registry/index.js +59 -0
  15. package/dist/src/index.d.ts +11 -0
  16. package/dist/src/index.js +14 -0
  17. package/dist/src/plugins/auth/basic.d.ts +1 -0
  18. package/dist/src/plugins/auth/basic.js +3 -1
  19. package/dist/src/plugins/builtins-preset.d.ts +5 -0
  20. package/dist/src/plugins/builtins-preset.js +32 -0
  21. package/dist/src/plugins/discover-addons.d.ts +22 -0
  22. package/dist/src/plugins/discover-addons.js +228 -0
  23. package/dist/src/plugins/executor/executor.d.ts +13 -8
  24. package/dist/src/plugins/preset.d.ts +76 -0
  25. package/dist/src/plugins/preset.js +42 -0
  26. package/dist/src/plugins/remote/drupal.d.ts +5 -0
  27. package/dist/src/plugins/remote/drupal.js +27 -1
  28. package/dist/src/plugins/remote/fake.d.ts +4 -0
  29. package/dist/src/plugins/remote/fake.js +5 -0
  30. package/dist/src/plugins/remote/remote.d.ts +12 -0
  31. package/dist/src/types.d.ts +14 -1
  32. package/dist/src/workflow/step-contract.d.ts +119 -0
  33. package/dist/src/workflow/step-contract.js +430 -0
  34. package/package.json +10 -4
@@ -0,0 +1,27 @@
1
+ import type { Command } from 'commander';
2
+ /**
3
+ * What the host hands each command plugin at mount time. `resolveBases` is the
4
+ * ESLint-style base order the plugin passes to `resolveModuleEslintStyle` for
5
+ * pnpm-safe sibling resolution (renderer, dropsh auth plugins) — the host owns
6
+ * it because only the host knows its own install location; a plugin resolving
7
+ * from its own `import.meta.url` breaks under pnpm's isolated store.
8
+ */
9
+ export interface GaiaCommandHost {
10
+ /** Base dirs/URLs for ESLint-style module resolution: host install → cwd. */
11
+ resolveBases: string[];
12
+ }
13
+ /**
14
+ * A command plugin the host mounts under its `name` (`gaia <name> …`).
15
+ * `describe` is shown in `gaia --help` even when the plugin is NOT loaded (the
16
+ * host renders a describe-only stub), so lazy mounting keeps `--help` complete.
17
+ * `register` receives the top-level `commander` program and the host; it adds
18
+ * the plugin's subcommand tree.
19
+ */
20
+ export interface GaiaCommandPlugin {
21
+ /** GAIA-215: the total-tag discriminant — a command plugin self-identifies its
22
+ * surface, like every other GAIA plugin carries a `.kind`. */
23
+ readonly kind: 'command';
24
+ name: string;
25
+ describe: string;
26
+ register(program: Command, host: GaiaCommandHost): void | Promise<void>;
27
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Walk from `cwd` root-ward (git/eslint style) to the nearest ancestor whose
3
+ * `.gaia/` dir holds at least one conductor config; return that `.gaia/` dir, or
4
+ * `undefined` if none is found up to the filesystem root.
5
+ */
6
+ export declare function findGaiaDir(cwd: string): string | undefined;
7
+ /**
8
+ * Resolve the ENGINE (`conductor.config.js`) config path from `cwd`.
9
+ *
10
+ * 1. An explicit `--config` / `$GAIA_CONDUCTOR_CONFIG` wins verbatim (no walk).
11
+ * 2. Otherwise walk root-ward to the nearest `.gaia/` dir holding a conductor
12
+ * config — so any subdirectory of a project/worktree resolves the same dir.
13
+ * - A `--conductor <name>` / `$GAIA_CONDUCTOR` selector resolves the stem's
14
+ * file (`conductor` → `conductor.config.js`, else `<name>.conductor.config.js`).
15
+ * - No selector: `conductor.config.js` present → the default; else exactly
16
+ * one config → use it (back-compat); else → error naming the stems.
17
+ * 3. No `.gaia/` config anywhere up the tree → an actionable `gaia init` error.
18
+ */
19
+ export declare function resolveConfigPath(override?: string, cwd?: string, conductorName?: string): string;
20
+ /** A resolved connection-config file: its path, whether it is the shipped
21
+ * machine-context fallback, and whether it is a legacy `conductor.config.js`. */
22
+ export interface GaiaConfigResolution {
23
+ path: string;
24
+ /** true only for the shipped fallback (needs `$GAIA_MACHINE_CONTEXT`). */
25
+ fallback: boolean;
26
+ /** true when the resolved file is a legacy `conductor.config.js` connection. */
27
+ legacy: boolean;
28
+ }
29
+ /**
30
+ * Walk root-ward for the nearest `.gaia/` connection config: prefer
31
+ * `gaia.config.js`; fall back to a legacy `conductor.config.js` in the same dir
32
+ * (back-compat). Returns `undefined` when neither is found up the tree.
33
+ */
34
+ export declare function findGaiaConfig(cwd: string): {
35
+ path: string;
36
+ legacy: boolean;
37
+ } | undefined;
38
+ /**
39
+ * Resolve the CONNECTION (`gaia.config.js`) config path. Precedence (AC-4):
40
+ * 1. `explicit` (`--config` / `$GAIA_CONFIG` / `$DROPSH_CONFIG`) wins verbatim.
41
+ * 2. Project `./.gaia/gaia.config.js` found by walk-up (legacy
42
+ * `conductor.config.js` accepted as a connection source).
43
+ * 3. Home `~/.gaia/gaia.config.js` when present.
44
+ * 4. The shipped fallback (`homeFallbackGaiaConfigPath`, reads the machine context).
45
+ */
46
+ export declare function resolveGaiaConfigPath(opts?: {
47
+ cwd?: string;
48
+ explicit?: string | undefined;
49
+ home?: string;
50
+ shippedFallback?: string;
51
+ }): GaiaConfigResolution;
@@ -0,0 +1,152 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { homeFallbackGaiaConfigPath } from './paths.js';
5
+ // GAIA-201: the pure `.gaia/` walk-up, hoisted from the conductor's `config.ts`.
6
+ // Two config kinds now live under `.gaia/`:
7
+ // - ENGINE `conductor.config.js` (+ `<variant>.conductor.config.js`) —
8
+ // project-only; resolved by `resolveConfigPath` (unchanged logic).
9
+ // - CONNECTION `gaia.config.js` — project walk-up → home `~/.gaia/gaia.config.js`
10
+ // → shipped fallback; resolved by `resolveGaiaConfigPath`. A legacy
11
+ // `conductor.config.js` still carrying `site`/`plugins` is accepted
12
+ // as a connection source (back-compat) when no `gaia.config.js` is
13
+ // found, so an un-migrated repo keeps working.
14
+ /** A repo's config files live in this dir, one conductor config per conductor. */
15
+ const GAIA_DIR = '.gaia';
16
+ /** The default conductor's file name; its stem is `conductor`. */
17
+ const DEFAULT_CONFIG = 'conductor.config.js';
18
+ /** Variant files are `<stem>.conductor.config.js`. */
19
+ const VARIANT_SUFFIX = '.conductor.config.js';
20
+ /** The connection config file name (project + home). */
21
+ const GAIA_CONFIG = 'gaia.config.js';
22
+ /**
23
+ * List the conductor-config stems in a `.gaia/` dir. A file is a conductor
24
+ * config iff it is exactly `conductor.config.js` (stem `conductor`, the
25
+ * default) or ends with `.conductor.config.js` (stem = the leading part).
26
+ * Every other file (`vite.config.js`, the near-miss `myconductor.config.js`,
27
+ * and `gaia.config.js` itself) is ignored.
28
+ */
29
+ function configStems(gaiaDir) {
30
+ return readdirSync(gaiaDir)
31
+ .map((f) => f === DEFAULT_CONFIG
32
+ ? 'conductor'
33
+ : f.endsWith(VARIANT_SUFFIX)
34
+ ? f.slice(0, -VARIANT_SUFFIX.length)
35
+ : undefined)
36
+ .filter((s) => s !== undefined)
37
+ .sort();
38
+ }
39
+ /** Map a conductor stem back to its file name. */
40
+ function fileForStem(stem) {
41
+ return stem === 'conductor' ? DEFAULT_CONFIG : `${stem}${VARIANT_SUFFIX}`;
42
+ }
43
+ /**
44
+ * Walk from `cwd` root-ward (git/eslint style) to the nearest ancestor whose
45
+ * `.gaia/` dir holds at least one conductor config; return that `.gaia/` dir, or
46
+ * `undefined` if none is found up to the filesystem root.
47
+ */
48
+ export function findGaiaDir(cwd) {
49
+ let dir = resolve(cwd);
50
+ for (;;) {
51
+ const gaiaDir = join(dir, GAIA_DIR);
52
+ if (existsSync(gaiaDir) && configStems(gaiaDir).length > 0) {
53
+ return gaiaDir;
54
+ }
55
+ const parent = dirname(dir);
56
+ if (parent === dir) {
57
+ return undefined;
58
+ }
59
+ dir = parent;
60
+ }
61
+ }
62
+ /**
63
+ * Resolve the ENGINE (`conductor.config.js`) config path from `cwd`.
64
+ *
65
+ * 1. An explicit `--config` / `$GAIA_CONDUCTOR_CONFIG` wins verbatim (no walk).
66
+ * 2. Otherwise walk root-ward to the nearest `.gaia/` dir holding a conductor
67
+ * config — so any subdirectory of a project/worktree resolves the same dir.
68
+ * - A `--conductor <name>` / `$GAIA_CONDUCTOR` selector resolves the stem's
69
+ * file (`conductor` → `conductor.config.js`, else `<name>.conductor.config.js`).
70
+ * - No selector: `conductor.config.js` present → the default; else exactly
71
+ * one config → use it (back-compat); else → error naming the stems.
72
+ * 3. No `.gaia/` config anywhere up the tree → an actionable `gaia init` error.
73
+ */
74
+ export function resolveConfigPath(override, cwd = process.cwd(), conductorName) {
75
+ const explicit = override ?? process.env.GAIA_CONDUCTOR_CONFIG;
76
+ if (explicit) {
77
+ return explicit;
78
+ }
79
+ const gaiaDir = findGaiaDir(cwd);
80
+ if (gaiaDir === undefined) {
81
+ throw new Error(`no .gaia/conductor.config.js found from ${cwd} upward; run \`gaia init\``);
82
+ }
83
+ const stems = configStems(gaiaDir);
84
+ const name = conductorName ?? process.env.GAIA_CONDUCTOR;
85
+ if (name !== undefined && name !== '') {
86
+ const candidate = join(gaiaDir, fileForStem(name));
87
+ if (!existsSync(candidate)) {
88
+ throw new Error(`no conductor '${name}' in ${gaiaDir}; available: ${stems.join(', ')}`);
89
+ }
90
+ return candidate;
91
+ }
92
+ if (stems.includes('conductor')) {
93
+ return join(gaiaDir, DEFAULT_CONFIG);
94
+ }
95
+ if (stems.length === 1) {
96
+ return join(gaiaDir, fileForStem(stems[0]));
97
+ }
98
+ throw new Error(`${stems.length} conductors in ${gaiaDir} (${stems.join(', ')}); ` +
99
+ 'select one with --conductor <name> or $GAIA_CONDUCTOR');
100
+ }
101
+ /**
102
+ * Walk root-ward for the nearest `.gaia/` connection config: prefer
103
+ * `gaia.config.js`; fall back to a legacy `conductor.config.js` in the same dir
104
+ * (back-compat). Returns `undefined` when neither is found up the tree.
105
+ */
106
+ export function findGaiaConfig(cwd) {
107
+ let dir = resolve(cwd);
108
+ for (;;) {
109
+ const gaiaDir = join(dir, GAIA_DIR);
110
+ if (existsSync(gaiaDir)) {
111
+ const gaiaCfg = join(gaiaDir, GAIA_CONFIG);
112
+ if (existsSync(gaiaCfg))
113
+ return { path: gaiaCfg, legacy: false };
114
+ const legacy = join(gaiaDir, DEFAULT_CONFIG);
115
+ if (existsSync(legacy))
116
+ return { path: legacy, legacy: true };
117
+ }
118
+ const parent = dirname(dir);
119
+ if (parent === dir)
120
+ return undefined;
121
+ dir = parent;
122
+ }
123
+ }
124
+ /**
125
+ * Resolve the CONNECTION (`gaia.config.js`) config path. Precedence (AC-4):
126
+ * 1. `explicit` (`--config` / `$GAIA_CONFIG` / `$DROPSH_CONFIG`) wins verbatim.
127
+ * 2. Project `./.gaia/gaia.config.js` found by walk-up (legacy
128
+ * `conductor.config.js` accepted as a connection source).
129
+ * 3. Home `~/.gaia/gaia.config.js` when present.
130
+ * 4. The shipped fallback (`homeFallbackGaiaConfigPath`, reads the machine context).
131
+ */
132
+ export function resolveGaiaConfigPath(opts = {}) {
133
+ const explicit = opts.explicit;
134
+ if (explicit !== undefined && explicit.trim() !== '') {
135
+ return { path: explicit, fallback: false, legacy: false };
136
+ }
137
+ const cwd = opts.cwd ?? process.cwd();
138
+ const project = findGaiaConfig(cwd);
139
+ if (project !== undefined) {
140
+ return { path: project.path, fallback: false, legacy: project.legacy };
141
+ }
142
+ const home = opts.home ?? homedir();
143
+ const homeCfg = join(home, GAIA_DIR, GAIA_CONFIG);
144
+ if (existsSync(homeCfg)) {
145
+ return { path: homeCfg, fallback: false, legacy: false };
146
+ }
147
+ return {
148
+ path: opts.shippedFallback ?? homeFallbackGaiaConfigPath(),
149
+ fallback: true,
150
+ legacy: false,
151
+ };
152
+ }
@@ -0,0 +1,25 @@
1
+ import type { DropSHPlugin } from 'dropsh/plugin';
2
+ import type { GaiaCommandHost } from './commands.js';
3
+ /** The resolved control-plane connection: site + constructed auth plugins. */
4
+ export interface GaiaConnectionConfig {
5
+ site: {
6
+ base_url: string;
7
+ jsonapi_prefix: string;
8
+ };
9
+ plugins: DropSHPlugin[];
10
+ /** Absolute path of the loaded connection config. */
11
+ config_path: string;
12
+ /** true when this is the shipped machine-context fallback. */
13
+ fallback: boolean;
14
+ }
15
+ /**
16
+ * Load the connection config for `gaia ui` / `gaia dropsh` / conductor-auth.
17
+ * Resolves the path (explicit → project walk-up → home → shipped fallback),
18
+ * sets `$GAIA_MACHINE_CONTEXT` before importing the shipped fallback, imports
19
+ * the module, reads ONLY `{ site, plugins }` (a legacy `conductor.config.js` is
20
+ * read the same way), and constructs the auth `plugins[]`.
21
+ */
22
+ export declare function loadGaiaConfig(host: GaiaCommandHost, opts?: {
23
+ cwd?: string;
24
+ explicit?: string | undefined;
25
+ }): Promise<GaiaConnectionConfig>;
@@ -0,0 +1,110 @@
1
+ import { dirname, resolve } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { discoverAddons } from '../plugins/discover-addons.js';
4
+ import { resolveGaiaConfigPath } from './gaia-dir.js';
5
+ import { resolveMachineContextPath } from './machine-context.js';
6
+ import { resolveModuleEslintStyle } from './resolve-module.js';
7
+ function isRecord(value) {
8
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
9
+ }
10
+ function isPluginDescriptor(entry) {
11
+ return isRecord(entry) && typeof entry.plugin === 'string';
12
+ }
13
+ /**
14
+ * Construct a `{ plugin, export?, with?/options? }` descriptor. Resolution base
15
+ * order: the config's own dir first (so a project config can name a locally
16
+ * installed plugin), then the host's `resolveBases` (host install → cwd) for
17
+ * pnpm-safe resolution of the shipped fallback's `@dropsh/plugin-*`.
18
+ */
19
+ async function loadDescriptor(entry, bases) {
20
+ const resolved = resolveModuleEslintStyle(entry.plugin, bases);
21
+ if (resolved === undefined) {
22
+ throw new Error(`gaia.config.js cannot resolve plugin '${entry.plugin}'`);
23
+ }
24
+ const mod = (await import(pathToFileURL(resolved).href));
25
+ let factory;
26
+ if (typeof entry.export === 'string') {
27
+ factory = mod[entry.export];
28
+ if (typeof factory !== 'function') {
29
+ throw new Error(`plugin '${entry.plugin}' has no callable export '${entry.export}'`);
30
+ }
31
+ }
32
+ else if (typeof mod.default === 'function') {
33
+ factory = mod.default;
34
+ }
35
+ else {
36
+ const fns = Object.keys(mod).filter((k) => typeof mod[k] === 'function');
37
+ if (fns.length === 1) {
38
+ factory = mod[fns[0]];
39
+ }
40
+ else {
41
+ throw new Error(`plugin '${entry.plugin}' has no default export and ${fns.length} function exports (${fns.join(', ')}); specify "export"`);
42
+ }
43
+ }
44
+ return factory(entry.with ?? entry.options);
45
+ }
46
+ /** Resolve the `plugins[]` array: descriptors constructed, constructed entries
47
+ * passed through; a factory returning an array is flattened one level (dropsh
48
+ * `composePlugins` semantics), mirroring the engine loader. */
49
+ async function resolvePlugins(raw, bases) {
50
+ if (!Array.isArray(raw))
51
+ return [];
52
+ const resolved = await Promise.all(raw.map((entry) => isPluginDescriptor(entry) ? loadDescriptor(entry, bases) : entry));
53
+ return resolved.flat();
54
+ }
55
+ /**
56
+ * Load the connection config for `gaia ui` / `gaia dropsh` / conductor-auth.
57
+ * Resolves the path (explicit → project walk-up → home → shipped fallback),
58
+ * sets `$GAIA_MACHINE_CONTEXT` before importing the shipped fallback, imports
59
+ * the module, reads ONLY `{ site, plugins }` (a legacy `conductor.config.js` is
60
+ * read the same way), and constructs the auth `plugins[]`.
61
+ */
62
+ export async function loadGaiaConfig(host, opts = {}) {
63
+ const resolution = resolveGaiaConfigPath({
64
+ ...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
65
+ explicit: opts.explicit,
66
+ });
67
+ const configPath = resolve(resolution.path);
68
+ // The shipped fallback derives the connection from the machine context, which
69
+ // it reads via this env var. Set it (canonical path) unless already set.
70
+ if (resolution.fallback && !process.env.GAIA_MACHINE_CONTEXT) {
71
+ // Prefer the canonical ~/.gaia/machine.config.js; fall back to the legacy
72
+ // path on an un-migrated machine so the shipped fallback still finds it.
73
+ process.env.GAIA_MACHINE_CONTEXT = resolveMachineContextPath();
74
+ }
75
+ const module = (await import(pathToFileURL(configPath).href));
76
+ const raw = module.default;
77
+ if (!isRecord(raw)) {
78
+ throw new Error(`gaia connection config default export must be an object (${configPath})`);
79
+ }
80
+ const site = isRecord(raw.site) ? raw.site : {};
81
+ const baseUrl = typeof site.base_url === 'string' ? site.base_url : '';
82
+ if (baseUrl.trim() === '') {
83
+ throw new Error(`gaia connection config requires site.base_url (${configPath})`);
84
+ }
85
+ const jsonapiPrefix = typeof site.jsonapi_prefix === 'string' && site.jsonapi_prefix.trim() !== ''
86
+ ? site.jsonapi_prefix
87
+ : '/jsonapi';
88
+ // Config dir first, then the host bases (pnpm-safe for the shipped fallback).
89
+ const bases = [
90
+ pathToFileURL(`${dirname(configPath)}/`).href,
91
+ ...host.resolveBases,
92
+ ];
93
+ // GAIA-215: the connection surface has TWO input forms that both still load —
94
+ // the legacy `plugins:[]` descriptor array AND the Storybook-style `addons:[]`
95
+ // discovered via the shared engine (connection surface = the auth/renderer
96
+ // plugins). Concatenate legacy first, then discovered.
97
+ const legacy = await resolvePlugins(raw.plugins, bases);
98
+ const discovered = Array.isArray(raw.addons)
99
+ ? await discoverAddons(raw.addons, 'connection', bases)
100
+ : undefined;
101
+ const plugins = discovered
102
+ ? [...legacy, ...discovered.connectionPlugins]
103
+ : legacy;
104
+ return {
105
+ site: { base_url: baseUrl, jsonapi_prefix: jsonapiPrefix },
106
+ plugins,
107
+ config_path: configPath,
108
+ fallback: resolution.fallback,
109
+ };
110
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The user-global machine context: identity + connection (incl. the OAuth
3
+ * client secret). Committed configs compose `machine_id` from it and read
4
+ * base_url / client_id / client_secret. Gitignored, user-only (chmod 0600).
5
+ */
6
+ export interface MachineContext {
7
+ machine_id: string;
8
+ user_id: string;
9
+ base_url: string;
10
+ client_id: string;
11
+ client_secret: string;
12
+ }
13
+ /** Canonical machine-context path: `~/.gaia/machine.config.js` (GAIA-201). */
14
+ export declare function machineContextPath(): string;
15
+ /** Legacy machine-context path (pre-GAIA-201), read as a fallback. */
16
+ export declare function legacyMachineContextPath(): string;
17
+ /**
18
+ * Resolve the machine-context path to read: the caller's explicit `path`, else
19
+ * the canonical `~/.gaia/machine.config.js` when it exists, else the legacy
20
+ * path. This is the single place the read-time canonical→legacy fallback lives.
21
+ */
22
+ export declare function resolveMachineContextPath(path?: string): string;
23
+ /** Import an existing context module's default export, or `{}` if absent/broken. */
24
+ export declare function readMachineContext(path?: string): Promise<Partial<MachineContext>>;
@@ -0,0 +1,45 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ /** Canonical machine-context path: `~/.gaia/machine.config.js` (GAIA-201). */
6
+ export function machineContextPath() {
7
+ return join(homedir(), '.gaia', 'machine.config.js');
8
+ }
9
+ /** Legacy machine-context path (pre-GAIA-201), read as a fallback. */
10
+ export function legacyMachineContextPath() {
11
+ return join(homedir(), '.config', 'conductor', 'conductor.config.machine.js');
12
+ }
13
+ /**
14
+ * Resolve the machine-context path to read: the caller's explicit `path`, else
15
+ * the canonical `~/.gaia/machine.config.js` when it exists, else the legacy
16
+ * path. This is the single place the read-time canonical→legacy fallback lives.
17
+ */
18
+ export function resolveMachineContextPath(path) {
19
+ if (path !== undefined && path !== '')
20
+ return path;
21
+ const canonical = machineContextPath();
22
+ if (existsSync(canonical))
23
+ return canonical;
24
+ const legacy = legacyMachineContextPath();
25
+ if (existsSync(legacy))
26
+ return legacy;
27
+ return canonical;
28
+ }
29
+ /** Import an existing context module's default export, or `{}` if absent/broken. */
30
+ export async function readMachineContext(path) {
31
+ const resolved = resolveMachineContextPath(path);
32
+ if (!existsSync(resolved))
33
+ return {};
34
+ try {
35
+ // Cache-bust so a re-render within one process re-reads fresh.
36
+ const mod = await import(`${pathToFileURL(resolved).href}?t=${Date.now()}`);
37
+ const raw = mod.default;
38
+ return raw && typeof raw === 'object'
39
+ ? raw
40
+ : {};
41
+ }
42
+ catch {
43
+ return {};
44
+ }
45
+ }
@@ -0,0 +1,11 @@
1
+ /** Walk from this module to the `@gaia-ai/core` package root (first ancestor
2
+ * holding a `package.json`). Works from `src/` (dev) and `dist/` (built). */
3
+ export declare function corePackageRoot(): string;
4
+ /**
5
+ * The shipped fallback connection config. It reads the machine context named by
6
+ * `$GAIA_MACHINE_CONTEXT` for base_url + credentials and constructs a `session`
7
+ * oauth2 plugin, so a home-rooted `gaia ui` / `gaia dropsh` works with no
8
+ * hand-authored config. `loadGaiaConfig` sets `$GAIA_MACHINE_CONTEXT` before
9
+ * importing it.
10
+ */
11
+ export declare function homeFallbackGaiaConfigPath(): string;
@@ -0,0 +1,31 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ // GAIA-201: filesystem anchors for the split config model. Core ships the
5
+ // fallback connection config `gaia.config.js` (used home-rooted when neither a
6
+ // project nor a home `~/.gaia/gaia.config.js` exists), so it must be able to
7
+ // find its own package root in both `src/` (dev via tsx) and `dist/` (built).
8
+ /** Walk from this module to the `@gaia-ai/core` package root (first ancestor
9
+ * holding a `package.json`). Works from `src/` (dev) and `dist/` (built). */
10
+ export function corePackageRoot() {
11
+ let dir = dirname(fileURLToPath(import.meta.url));
12
+ for (;;) {
13
+ if (existsSync(join(dir, 'package.json')))
14
+ return dir;
15
+ const parent = dirname(dir);
16
+ if (parent === dir) {
17
+ throw new Error('@gaia-ai/core package root not found');
18
+ }
19
+ dir = parent;
20
+ }
21
+ }
22
+ /**
23
+ * The shipped fallback connection config. It reads the machine context named by
24
+ * `$GAIA_MACHINE_CONTEXT` for base_url + credentials and constructs a `session`
25
+ * oauth2 plugin, so a home-rooted `gaia ui` / `gaia dropsh` works with no
26
+ * hand-authored config. `loadGaiaConfig` sets `$GAIA_MACHINE_CONTEXT` before
27
+ * importing it.
28
+ */
29
+ export function homeFallbackGaiaConfigPath() {
30
+ return join(corePackageRoot(), 'gaia.config.js');
31
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Resolve `spec` against `bases`, ESLint-style: the first base whose
3
+ * `createRequire(base).resolve(spec)` succeeds wins; returns that absolute
4
+ * filesystem path. Returns `undefined` when no base resolves it.
5
+ */
6
+ export declare function resolveModuleEslintStyle(spec: string, bases: string[]): string | undefined;
@@ -0,0 +1,24 @@
1
+ import { createRequire } from 'node:module';
2
+ // GAIA-201: the ESLint-style module resolver, hoisted from the conductor's
3
+ // `config.ts` (`loadNamedPlugin`). A plugin name is resolved by trying each base
4
+ // in order (config dir → cwd → install) and returning the first
5
+ // `createRequire(base).resolve(name)` that succeeds. Bases are node module
6
+ // parents — an absolute file path OR a `file://` URL string; both are accepted
7
+ // by `createRequire`. Keeping this pure + shared lets the host, the connection
8
+ // loader, and the engine plugin loader all resolve identically.
9
+ /**
10
+ * Resolve `spec` against `bases`, ESLint-style: the first base whose
11
+ * `createRequire(base).resolve(spec)` succeeds wins; returns that absolute
12
+ * filesystem path. Returns `undefined` when no base resolves it.
13
+ */
14
+ export function resolveModuleEslintStyle(spec, bases) {
15
+ for (const base of bases) {
16
+ try {
17
+ return createRequire(base).resolve(spec);
18
+ }
19
+ catch {
20
+ // try the next base
21
+ }
22
+ }
23
+ return undefined;
24
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * One registered conductor in the local machine registry
3
+ * (`${GAIA_HOME ?? ~}/.gaia/conductors.json`). The registry is inherently
4
+ * machine + user scoped: it lists the conductors this user can drive locally.
5
+ */
6
+ export interface ConductorRegistryEntry {
7
+ id: string;
8
+ path: string;
9
+ project: string;
10
+ label: string;
11
+ host: 'herdr' | 'process' | 'systemd';
12
+ handle: string;
13
+ }
14
+ /** `${GAIA_HOME ?? ~}/.gaia/conductors.json`. */
15
+ export declare function conductorRegistryPath(): string;
16
+ /** All registered conductors, in insertion order. */
17
+ export declare function listRegisteredConductors(): Promise<ConductorRegistryEntry[]>;
18
+ /** One registered conductor by id, or `null` when absent. */
19
+ export declare function getRegisteredConductor(id: string): Promise<ConductorRegistryEntry | null>;
20
+ /** Insert or overwrite a conductor by id (idempotent). */
21
+ export declare function registerConductor(entry: ConductorRegistryEntry): Promise<void>;
22
+ /** Remove a conductor by id; a no-op when it is not present. */
23
+ export declare function removeConductor(id: string): Promise<void>;
@@ -0,0 +1,59 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ /** `${GAIA_HOME ?? ~}/.gaia/conductors.json`. */
5
+ export function conductorRegistryPath() {
6
+ const home = process.env.GAIA_HOME ?? homedir();
7
+ return join(home, '.gaia', 'conductors.json');
8
+ }
9
+ async function read() {
10
+ let raw;
11
+ try {
12
+ raw = await readFile(conductorRegistryPath(), 'utf8');
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ if (raw.trim() === '') {
18
+ return {};
19
+ }
20
+ try {
21
+ const parsed = JSON.parse(raw);
22
+ if (typeof parsed === 'object' &&
23
+ parsed !== null &&
24
+ !Array.isArray(parsed)) {
25
+ return parsed;
26
+ }
27
+ return {};
28
+ }
29
+ catch {
30
+ return {};
31
+ }
32
+ }
33
+ async function write(data) {
34
+ const path = conductorRegistryPath();
35
+ await mkdir(dirname(path), { recursive: true });
36
+ await writeFile(path, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
37
+ }
38
+ /** All registered conductors, in insertion order. */
39
+ export async function listRegisteredConductors() {
40
+ return Object.values(await read());
41
+ }
42
+ /** One registered conductor by id, or `null` when absent. */
43
+ export async function getRegisteredConductor(id) {
44
+ return (await read())[id] ?? null;
45
+ }
46
+ /** Insert or overwrite a conductor by id (idempotent). */
47
+ export async function registerConductor(entry) {
48
+ const data = await read();
49
+ data[entry.id] = entry;
50
+ await write(data);
51
+ }
52
+ /** Remove a conductor by id; a no-op when it is not present. */
53
+ export async function removeConductor(id) {
54
+ const data = await read();
55
+ if (data[id]) {
56
+ delete data[id];
57
+ await write(data);
58
+ }
59
+ }
@@ -1,13 +1,24 @@
1
+ export type { GaiaCommandHost, GaiaCommandPlugin } from './cli/commands.js';
2
+ export type { GaiaConfigResolution } from './cli/gaia-dir.js';
3
+ export { findGaiaConfig, findGaiaDir, resolveConfigPath, resolveGaiaConfigPath, } from './cli/gaia-dir.js';
4
+ export { type GaiaConnectionConfig, loadGaiaConfig, } from './cli/load-gaia-config.js';
5
+ export { legacyMachineContextPath, type MachineContext, machineContextPath, readMachineContext, resolveMachineContextPath, } from './cli/machine-context.js';
6
+ export { corePackageRoot, homeFallbackGaiaConfigPath, } from './cli/paths.js';
7
+ export { resolveModuleEslintStyle } from './cli/resolve-module.js';
8
+ export { type ConductorRegistryEntry, conductorRegistryPath, getRegisteredConductor, listRegisteredConductors, registerConductor, removeConductor, } from './conductor-registry/index.js';
1
9
  export { conductorId } from './core/conductor-id.js';
2
10
  export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core/exec.js';
3
11
  export { type ConductorLogger, createLogger } from './core/logger.js';
4
12
  export { shellQuote } from './core/shell.js';
5
13
  export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
6
14
  export { type AgentFootprint, emptyAgentFootprint, type GaiaAgent, } from './plugins/agent/agent.js';
15
+ export { discoverAddons, type ResolvedConductorSlots, resolveConductorSlots, } from './plugins/discover-addons.js';
7
16
  export type * from './plugins/executor/executor.js';
8
17
  export type { AgentCandidate, AgentPlugin, ExecutorDeps, ExecutorPlugin, RemotePlugin, ResolvedAgent, WorkspacePlugin, } from './plugins/plugins.js';
9
18
  export { selectAgent, selectAgents } from './plugins/plugins.js';
19
+ export { type AddonEntry, type CommandDescriptor, type DiscoveredContributions, emptyContributions, type GaiaPreset, type GaiaSurface, PRESET_FUNCTION_KEYS, SURFACE_KEYS, } from './plugins/preset.js';
10
20
  export type * from './plugins/remote/remote.js';
11
21
  export { loadInstructions } from './plugins/workspace/instructions.js';
12
22
  export type { EnsuredWorkspace, GaiaWorkspace, } from './plugins/workspace/workspace.js';
13
23
  export type * from './types.js';
24
+ export { type ExpandedLoad, expandLoad, type InputDecl, parseStepValues, readSkillWhen, type SkillContract, type SkillWhen, StepContractError, type StepValues, type Triple, validateLoad, type WhenValue, WORKFLOW_STEPS, type WorkflowStep, } from './workflow/step-contract.js';
package/dist/src/index.js CHANGED
@@ -1,8 +1,22 @@
1
+ export { findGaiaConfig, findGaiaDir, resolveConfigPath, resolveGaiaConfigPath, } from './cli/gaia-dir.js';
2
+ export { loadGaiaConfig, } from './cli/load-gaia-config.js';
3
+ export { legacyMachineContextPath, machineContextPath, readMachineContext, resolveMachineContextPath, } from './cli/machine-context.js';
4
+ export { corePackageRoot, homeFallbackGaiaConfigPath, } from './cli/paths.js';
5
+ export { resolveModuleEslintStyle } from './cli/resolve-module.js';
6
+ export { conductorRegistryPath, getRegisteredConductor, listRegisteredConductors, registerConductor, removeConductor, } from './conductor-registry/index.js';
1
7
  export { conductorId } from './core/conductor-id.js';
2
8
  export { CommandRunner, ExecError, exec, setDefaultCommandRunner, } from './core/exec.js';
3
9
  export { createLogger } from './core/logger.js';
4
10
  export { shellQuote } from './core/shell.js';
5
11
  export { DEFAULT_SLUG_MAX_LENGTH, slugify } from './core/slug.js';
6
12
  export { emptyAgentFootprint, } from './plugins/agent/agent.js';
13
+ // GAIA-215: the Storybook-style preset contract + the shared addon discovery.
14
+ export { discoverAddons, resolveConductorSlots, } from './plugins/discover-addons.js';
7
15
  export { selectAgent, selectAgents } from './plugins/plugins.js';
16
+ export { emptyContributions, PRESET_FUNCTION_KEYS, SURFACE_KEYS, } from './plugins/preset.js';
17
+ // GAIA-194 AC-2: the agent-host contract (`AgentLaunchHost` / `HostedAgent` /
18
+ // `supportsAgentHost`) moved OUT of core — it is the TUI renderer's own plugin
19
+ // seam (`@gaia-ai/plugin-gaia-ui`), and herdr types its impl locally. Core is
20
+ // the conductor engine contract only.
8
21
  export { loadInstructions } from './plugins/workspace/instructions.js';
22
+ export { expandLoad, parseStepValues, readSkillWhen, StepContractError, validateLoad, WORKFLOW_STEPS, } from './workflow/step-contract.js';