From 2cc5a0d61261695472543e20132df5bed6632a89 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 14 May 2026 15:06:45 -0700 Subject: [PATCH] Sync integrations snapshot 386873d --- packages/cli/CHANGELOG.md | 13 ++++++++ packages/cli/package.json | 3 +- .../__tests__/interactive-dashboard.test.ts | 28 ++++++++++++++++- .../src/__tests__/subprocess-smoke.test.ts | 30 +++++++++++++++---- packages/cli/src/cli/runtime.ts | 10 +++++-- 5 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 packages/cli/CHANGELOG.md diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md new file mode 100644 index 0000000..058e68f --- /dev/null +++ b/packages/cli/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## 0.1.1 - 2026-05-14 + +### Fixed + +- Fixed interactive dashboard commands reusing a provider-free startup state after launching from a bare `atomicmemory` invocation. Dashboard sessions now hydrate the saved profile and scope at startup, while plain provider-free commands such as `atomicmemory help` remain provider-free. + +## 0.1.0 - 2026-05-14 + +### Added + +- Initial public release of the AtomicMemory CLI. diff --git a/packages/cli/package.json b/packages/cli/package.json index 1f35f8e..e5e3fcc 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@atomicmemory/cli", - "version": "0.1.0", + "version": "0.1.1", "description": "AtomicMemory CLI for memory operations, config, status, and agent-friendly JSON output.", "type": "module", "publishConfig": { @@ -19,6 +19,7 @@ "atomicmemory": "dist/bin.js" }, "files": [ + "CHANGELOG.md", "cli-spec.json", "config.schema.json", "dist", diff --git a/packages/cli/src/__tests__/interactive-dashboard.test.ts b/packages/cli/src/__tests__/interactive-dashboard.test.ts index 0ff01b7..c41dfa4 100644 --- a/packages/cli/src/__tests__/interactive-dashboard.test.ts +++ b/packages/cli/src/__tests__/interactive-dashboard.test.ts @@ -20,7 +20,12 @@ import { createInteractiveRuntimeSession, mergeInteractiveFlags, } from '../cli/interactive-session.js'; -import { shouldLaunchDashboard, type RuntimeState } from '../cli/runtime.js'; +import { + resolveRuntimeProfile, + resolveRuntimeScope, + shouldLaunchDashboard, + type RuntimeState, +} from '../cli/runtime.js'; test('splitShellWords supports quoted dashboard command input', () => { assert.deepEqual(splitShellWords('search "release policy" --namespace docs'), [ @@ -86,6 +91,27 @@ test('shouldLaunchDashboard only launches for bare or explicit interactive invoc assert.equal(shouldLaunchDashboard({ path: 'doctor', positional: [], flags: {} }), false); }); +test('bare dashboard hydrates saved profile and scope for cached commands', () => { + const config = sessionConfig('user-from-profile'); + const invocation = { path: 'help', positional: [], flags: {}, source: 'bare' } as const; + const profile = resolveRuntimeProfile(invocation, config, 'default', {}); + const scope = resolveRuntimeScope(invocation, profile, {}); + + assert.equal(profile?.provider, 'atomicmemory'); + assert.equal(profile?.apiUrl, 'http://localhost:3050'); + assert.deepEqual(scope, { user: 'user-from-profile' }); +}); + +test('plain help stays provider-free outside dashboard mode', () => { + const config = sessionConfig('user-from-profile'); + const invocation = { path: 'help', positional: [], flags: {}, source: 'help_flag' } as const; + const profile = resolveRuntimeProfile(invocation, config, 'default', {}); + const scope = resolveRuntimeScope(invocation, profile, {}); + + assert.equal(profile, null); + assert.deepEqual(scope, {}); +}); + test('mergeInteractiveFlags inherits only profile, provider, scope, and config flags', () => { const merged = mergeInteractiveFlags( { diff --git a/packages/cli/src/__tests__/subprocess-smoke.test.ts b/packages/cli/src/__tests__/subprocess-smoke.test.ts index 04e5e53..b86f2ce 100644 --- a/packages/cli/src/__tests__/subprocess-smoke.test.ts +++ b/packages/cli/src/__tests__/subprocess-smoke.test.ts @@ -22,14 +22,37 @@ import { dirname, resolve } from 'node:path'; const here = dirname(fileURLToPath(import.meta.url)); const cliRoot = resolve(here, '..', '..'); const binPath = resolve(cliRoot, 'dist', 'bin.js'); +const missingConfigPath = resolve(cliRoot, '.subprocess-smoke-missing-config.json'); +const ATOMICMEMORY_ENV_KEYS = [ + 'ATOMICMEMORY_API_KEY', + 'ATOMICMEMORY_API_URL', + 'ATOMICMEMORY_CONFIG', + 'ATOMICMEMORY_PROVIDER', + 'ATOMICMEMORY_SCOPE_AGENT_ID', + 'ATOMICMEMORY_SCOPE_NAMESPACE', + 'ATOMICMEMORY_SCOPE_THREAD', + 'ATOMICMEMORY_SCOPE_USER', + 'ATOMICMEMORY_TRUST_SURFACE', +] as const; + +function isolatedSubprocessEnv(overrides: NodeJS.ProcessEnv): NodeJS.ProcessEnv { + const env = { ...process.env }; + for (const key of ATOMICMEMORY_ENV_KEYS) delete env[key]; + return { + ...env, + ATOMICMEMORY_CONFIG: missingConfigPath, + ...overrides, + NO_COLOR: '1', + }; +} function runBin( args: readonly string[], - env: NodeJS.ProcessEnv = process.env, + env: NodeJS.ProcessEnv = {}, ): { stdout: string; stderr: string; code: number } { const r = spawnSync(process.execPath, [binPath, ...args], { encoding: 'utf8', - env: { ...env, NO_COLOR: '1' }, + env: isolatedSubprocessEnv(env), }); return { stdout: r.stdout ?? '', @@ -76,10 +99,7 @@ test('subprocess: --json help emits exactly one parseable JSON document, no prea }); test('subprocess: hooks install is provider-free even with partial provider env', { skip: skipIfUnbuilt }, () => { - const env = { ...process.env }; - delete env.ATOMICMEMORY_TRUST_SURFACE; const r = runBin(['--json', 'hooks', 'install', '--host', 'codex'], { - ...env, ATOMICMEMORY_PROVIDER: 'atomicmemory', ATOMICMEMORY_API_URL: 'https://core.example.invalid', }); diff --git a/packages/cli/src/cli/runtime.ts b/packages/cli/src/cli/runtime.ts index 29fd90d..10959d3 100644 --- a/packages/cli/src/cli/runtime.ts +++ b/packages/cli/src/cli/runtime.ts @@ -162,7 +162,7 @@ function loadRuntimeConfig( return { paths, config, profileName }; } -function resolveRuntimeProfile( +export function resolveRuntimeProfile( invocation: Invocation, config: CliConfigShape, profileName: string, @@ -171,7 +171,7 @@ function resolveRuntimeProfile( if (invocation.path === 'init') { return config.profiles[profileName] ?? null; } - if (isProviderFreeInvocation(invocation)) { + if (isProviderFreeInvocation(invocation) && !isDashboardInvocation(invocation)) { return null; } const baseProfile = resolveBaseProfile(invocation.flags, config, profileName, env); @@ -183,7 +183,11 @@ function isProviderFreeInvocation(invocation: Invocation): boolean { return invocation.path === 'hooks' && invocation.positional[0] === 'install'; } -function resolveRuntimeScope( +function isDashboardInvocation(invocation: Invocation): boolean { + return invocation.source === 'bare' || invocation.flags.interactive === true; +} + +export function resolveRuntimeScope( invocation: Invocation, profile: CliProfileShape | null, env: NodeJS.ProcessEnv,