@gaia-ai/gaia 0.15.0 → 0.15.2

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 (2) hide show
  1. package/dist/src/config.js +78 -6
  2. package/package.json +24 -24
@@ -1,8 +1,8 @@
1
- import { homedir, hostname } from 'node:os';
1
+ import { hostname } from 'node:os';
2
2
  import { join } from 'node:path';
3
3
  import { createInterface } from 'node:readline';
4
4
  import { pathToFileURL } from 'node:url';
5
- import { reportLoadedGaiaConfigFields, resolveGaiaConnectionSelection, } from '@gaia-ai/core';
5
+ import { homeGaiaDir, reportLoadedGaiaConfigFields, resolveGaiaConnectionSelection, } from '@gaia-ai/core';
6
6
  import { Option } from 'commander';
7
7
  /** Terminal-only input. Secret characters are never echoed. */
8
8
  async function promptInput(label, secret = false) {
@@ -42,6 +42,10 @@ export function renderConfigReport(report, dryRun = false) {
42
42
  ];
43
43
  for (const file of report.files.filter((file) => file.selected))
44
44
  lines.push(`${file.layer}: ${file.path} (${file.state}; schema ${file.version.effective ?? 'unversioned'} → ${file.version.current ?? 'unversioned'}; ${file.fingerprint ?? 'absent'})`);
45
+ if (report.machine_policy) {
46
+ const { tunnel, source } = report.machine_policy;
47
+ lines.push(`Machine tunnel: ${tunnel === null ? 'unresolved' : tunnel} (from ${source})`);
48
+ }
45
49
  if (report.connection_resolution) {
46
50
  const selected = report.connection_resolution;
47
51
  lines.push(`Connection: ${selected.id} (${selected.selected_by}) — ${selected.path}`);
@@ -102,6 +106,8 @@ function buildConfigCommand(parent, operation, deps, hiddenScope, description) {
102
106
  .option('--workspace-id <uuid>', 'explicit workspace UUID')
103
107
  .option('--workspace <name>', 'committed project workspace name')
104
108
  .option('--secret-env <name>', 'environment variable containing the OAuth secret')
109
+ .option('--after-create <command>', "committed conductor config's after_create hook; empty means none, gaia-cli names no provisioning tool")
110
+ .option('--after-done <command>', "committed conductor config's after_done hook; empty means none, gaia-cli names no provisioning tool")
105
111
  .addOption(new Option('--auth-type <type>', 'sign in with device code (default, no secret) or a client secret (internal/CI)').choices(['client_credentials', 'device']));
106
112
  command.action(async (options) => {
107
113
  if (hiddenScope !== undefined && options.scope === undefined)
@@ -149,10 +155,17 @@ function buildConfigCommand(parent, operation, deps, hiddenScope, description) {
149
155
  options.authType === 'client_credentials'
150
156
  ? 'oauth2_client_credentials'
151
157
  : 'oauth2_device_code';
158
+ if (options.afterCreate !== undefined || options.afterDone !== undefined)
159
+ inputs.hooks = {
160
+ ...(options.afterCreate ? { after_create: options.afterCreate } : {}),
161
+ ...(options.afterDone ? { after_done: options.afterDone } : {}),
162
+ };
152
163
  const planning = {
153
164
  operation,
154
165
  cwd,
155
- home: deps.home ?? homedir(),
166
+ // Only forward an explicitly injected home; absent, core resolves the
167
+ // `.gaia` root itself and honours GAIA_HOME.
168
+ ...(deps.home !== undefined ? { home: deps.home } : {}),
156
169
  inputs,
157
170
  // `gaia init` (hiddenScope !== undefined) never pulls the project
158
171
  // layer in, even inside a project checkout (GAIA-496 finding 1).
@@ -168,7 +181,7 @@ function buildConfigCommand(parent, operation, deps, hiddenScope, description) {
168
181
  else if (options.connection !== undefined) {
169
182
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(options.connection))
170
183
  throw new Error('--connection must be an identifier containing letters, numbers, dot, underscore or hyphen.');
171
- planning.connectionPath = join(deps.home ?? homedir(), '.gaia', 'connections', `${options.connection}.js`);
184
+ planning.connectionPath = join(homeGaiaDir(deps.home), 'connections', `${options.connection}.js`);
172
185
  }
173
186
  let selectedConnection;
174
187
  const initializesNamedConnection = operation === 'init' &&
@@ -177,7 +190,12 @@ function buildConfigCommand(parent, operation, deps, hiddenScope, description) {
177
190
  if (!initializesNamedConnection && options.scope !== 'machine') {
178
191
  selectedConnection = await resolveGaiaConnectionSelection({
179
192
  cwd,
180
- home: deps.home ?? homedir(),
193
+ // Only forward an explicitly injected home; absent, core resolves
194
+ // the `.gaia` root itself and honours GAIA_HOME (same rule as the
195
+ // `planning` object above — a stray `?? homedir()` here would
196
+ // resolve this command's connection selection under `~/.gaia` while
197
+ // the rest of it honours the override).
198
+ ...(deps.home !== undefined ? { home: deps.home } : {}),
181
199
  env,
182
200
  ...(options.config ? { explicit: options.config } : {}),
183
201
  ...(options.connection ? { connection: options.connection } : {}),
@@ -291,8 +309,46 @@ function buildConfigCommand(parent, operation, deps, hiddenScope, description) {
291
309
  report.verification = current.verification;
292
310
  }
293
311
  }
312
+ // A device-code connection authenticates from a session that sign-in
313
+ // creates, and nothing has signed in yet when `init` has only just
314
+ // written the file. Verifying here therefore reports
315
+ // `authentication_failure` on a perfectly correct machine — a symptom
316
+ // named in place of its cause, and the reason `gaia init` used to end
317
+ // `failed` on every fresh setup. Writing the connection and signing in
318
+ // are two steps, so `init` reports the second as outstanding instead.
319
+ // An explicit `--online` still verifies, so the deferral is gated on
320
+ // the whole predicate rather than on the file read alone — otherwise
321
+ // `--auth-type device` defers the very check the operator asked for.
322
+ // Read the grant off the connection that is about to be verified, not
323
+ // off `--auth-type`: a repeat `gaia init` supplies no flag and writes
324
+ // nothing, and keying on the input would leave exactly that run — the
325
+ // one an operator uses as a diagnostic — reporting a failure again. A
326
+ // read that fails falls through to verifying, so a broken file is
327
+ // reported rather than quietly skipped.
328
+ let connectionGrant;
329
+ if (operation === 'init' && planning.connectionPath)
330
+ try {
331
+ const fields = await reportLoadedGaiaConfigFields(planning.connectionPath);
332
+ // Locate the OAuth plugin by identity. Its grant is the only one
333
+ // that says anything about signing in, and any plugin may carry a
334
+ // `with.type` of its own, so matching on field shape alone reads
335
+ // whichever happens to come first.
336
+ const prefix = fields
337
+ .find((field) => /^plugins\[\d+\]\.plugin$/.test(field.key) &&
338
+ field.value === '@dropsh/plugin-oauth2')
339
+ ?.key.replace(/\.plugin$/, '');
340
+ if (prefix !== undefined)
341
+ connectionGrant = fields.find((field) => field.key === `${prefix}.with.type`)?.value;
342
+ }
343
+ catch {
344
+ // Inventory findings already describe an unreadable or malformed file.
345
+ }
346
+ const awaitsSignIn = !options.online &&
347
+ (inputs.authType === 'oauth2_device_code' ||
348
+ connectionGrant === 'oauth2_device_code');
294
349
  if (!cancelled &&
295
350
  !options.dryRun &&
351
+ !awaitsSignIn &&
296
352
  (operation === 'init' || options.online) &&
297
353
  !report.findings.some((f) => f.severity === 'error')) {
298
354
  const { verifyConfigConnections } = await import('@gaia-ai/core');
@@ -314,7 +370,23 @@ function buildConfigCommand(parent, operation, deps, hiddenScope, description) {
314
370
  else if (!options.dryRun &&
315
371
  (operation === 'init' || options.online) &&
316
372
  report.verification.online.status !== 'not_applicable') {
317
- const reason = 'Online validation skipped because offline errors require repair or input was cancelled.';
373
+ // `gaia init` is also the diagnostic an operator re-runs on a finished
374
+ // setup. Naming sign-in as the next step there is the one instruction
375
+ // that is certainly wrong, so say which of the two situations this is.
376
+ const { hasStoredGaiaSession } = await import('@gaia-ai/core');
377
+ const reason = awaitsSignIn
378
+ ? (await hasStoredGaiaSession(deps.host ?? {
379
+ resolveBases: [import.meta.url, pathToFileURL(`${cwd}/`).href],
380
+ }, {
381
+ cwd,
382
+ ...(planning.connectionPath
383
+ ? { explicit: planning.connectionPath }
384
+ : {}),
385
+ ...(deps.stateDir ? { stateDir: deps.stateDir } : {}),
386
+ }))
387
+ ? 'A session for this connection already exists; verify it with gaia config check --scope connection --online.'
388
+ : 'Sign-in is the next step: run gaia dropsh auth login, then gaia config check --scope connection --online.'
389
+ : 'Online validation skipped because offline errors require repair or input was cancelled.';
318
390
  report.verification.online.reason = reason;
319
391
  report.verification.skipped.push({
320
392
  path: report.roots.connection,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaia-ai/gaia",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "GAIA meta package: the `gaia` plugin-host CLI + all runtime plugins. Global install target.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,29 +38,29 @@
38
38
  "jsonapi"
39
39
  ],
40
40
  "dependencies": {
41
- "@gaia-ai/addon-agents": "^0.15.0",
42
- "@gaia-ai/addon-auth-basic": "^0.15.0",
43
- "@gaia-ai/addon-claude": "^0.15.0",
44
- "@gaia-ai/addon-codex": "^0.15.0",
45
- "@gaia-ai/addon-deployment": "^0.15.0",
46
- "@gaia-ai/addon-dropsh": "^0.15.0",
47
- "@gaia-ai/addon-essentials": "^0.15.0",
48
- "@gaia-ai/addon-gaia-ui": "^0.15.0",
49
- "@gaia-ai/addon-gaia-ui-artifacts": "^0.15.0",
50
- "@gaia-ai/addon-grok": "^0.15.0",
51
- "@gaia-ai/addon-herdr": "^0.15.0",
52
- "@gaia-ai/addon-info": "^0.15.0",
53
- "@gaia-ai/addon-kimi": "^0.15.0",
54
- "@gaia-ai/addon-opencode": "^0.15.0",
55
- "@gaia-ai/addon-pi": "^0.15.0",
56
- "@gaia-ai/addon-ports": "^0.15.0",
57
- "@gaia-ai/addon-ports-ui": "^0.15.0",
58
- "@gaia-ai/addon-remote-drupal": "^0.15.0",
59
- "@gaia-ai/addon-workspace": "^0.15.0",
60
- "@gaia-ai/addon-workspace-git": "^0.15.0",
61
- "@gaia-ai/conductor": "^0.15.0",
62
- "@gaia-ai/core": "^0.15.0",
63
- "@gaia-ai/ui": "^0.15.0",
41
+ "@gaia-ai/addon-agents": "^0.15.2",
42
+ "@gaia-ai/addon-auth-basic": "^0.15.2",
43
+ "@gaia-ai/addon-claude": "^0.15.2",
44
+ "@gaia-ai/addon-codex": "^0.15.2",
45
+ "@gaia-ai/addon-deployment": "^0.15.2",
46
+ "@gaia-ai/addon-dropsh": "^0.15.2",
47
+ "@gaia-ai/addon-essentials": "^0.15.2",
48
+ "@gaia-ai/addon-gaia-ui": "^0.15.2",
49
+ "@gaia-ai/addon-gaia-ui-artifacts": "^0.15.2",
50
+ "@gaia-ai/addon-grok": "^0.15.2",
51
+ "@gaia-ai/addon-herdr": "^0.15.2",
52
+ "@gaia-ai/addon-info": "^0.15.2",
53
+ "@gaia-ai/addon-kimi": "^0.15.2",
54
+ "@gaia-ai/addon-opencode": "^0.15.2",
55
+ "@gaia-ai/addon-pi": "^0.15.2",
56
+ "@gaia-ai/addon-ports": "^0.15.2",
57
+ "@gaia-ai/addon-ports-ui": "^0.15.2",
58
+ "@gaia-ai/addon-remote-drupal": "^0.15.2",
59
+ "@gaia-ai/addon-workspace": "^0.15.2",
60
+ "@gaia-ai/addon-workspace-git": "^0.15.2",
61
+ "@gaia-ai/conductor": "^0.15.2",
62
+ "@gaia-ai/core": "^0.15.2",
63
+ "@gaia-ai/ui": "^0.15.2",
64
64
  "commander": "^12.1.0"
65
65
  }
66
66
  }