@gaia-ai/core 0.5.5 → 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.
- package/dist/src/cli/commands.d.ts +27 -0
- package/dist/src/cli/commands.js +1 -0
- package/dist/src/cli/gaia-dir.d.ts +51 -0
- package/dist/src/cli/gaia-dir.js +152 -0
- package/dist/src/cli/load-gaia-config.d.ts +25 -0
- package/dist/src/cli/load-gaia-config.js +110 -0
- package/dist/src/cli/machine-context.d.ts +24 -0
- package/dist/src/cli/machine-context.js +45 -0
- package/dist/src/cli/paths.d.ts +11 -0
- package/dist/src/cli/paths.js +31 -0
- package/dist/src/cli/resolve-module.d.ts +6 -0
- package/dist/src/cli/resolve-module.js +24 -0
- package/dist/src/conductor-registry/index.d.ts +23 -0
- package/dist/src/conductor-registry/index.js +59 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +14 -0
- package/dist/src/plugins/auth/basic.d.ts +1 -0
- package/dist/src/plugins/auth/basic.js +3 -1
- package/dist/src/plugins/builtins-preset.d.ts +5 -0
- package/dist/src/plugins/builtins-preset.js +32 -0
- package/dist/src/plugins/discover-addons.d.ts +22 -0
- package/dist/src/plugins/discover-addons.js +228 -0
- package/dist/src/plugins/preset.d.ts +76 -0
- package/dist/src/plugins/preset.js +42 -0
- package/dist/src/plugins/remote/drupal.d.ts +5 -0
- package/dist/src/plugins/remote/drupal.js +24 -0
- package/dist/src/plugins/remote/fake.d.ts +4 -0
- package/dist/src/plugins/remote/fake.js +4 -0
- package/dist/src/plugins/remote/remote.d.ts +10 -0
- package/dist/src/types.d.ts +14 -1
- package/dist/src/workflow/step-contract.d.ts +119 -0
- package/dist/src/workflow/step-contract.js +430 -0
- 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
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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';
|