aos-harness 0.7.1 → 0.8.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/README.md CHANGED
@@ -19,6 +19,13 @@ npm i -g aos-harness
19
19
 
20
20
  ### 2. Install an adapter
21
21
 
22
+ Install the vendor CLI you want to drive first, then install the matching AOS adapter package. The adapter is the AOS integration layer on top of the vendor CLI:
23
+
24
+ - `claude` + `@aos-harness/claude-code-adapter`
25
+ - `codex` + `@aos-harness/codex-adapter`
26
+ - `gemini` + `@aos-harness/gemini-adapter`
27
+ - `pi` + `@aos-harness/pi-adapter`
28
+
22
29
  Pick the AI CLI you'll drive agents with and install the matching adapter. You can install more than one. Versions are lockstep — pin the adapter to the same version as the CLI.
23
30
 
24
31
  ```bash
@@ -34,6 +41,12 @@ npm i -g @aos-harness/pi-adapter # Pi (https://pi.dev)
34
41
  # Initialize a project (writes .aos/ and copies core/ into the project)
35
42
  aos init
36
43
 
44
+ # Or scan only in CI / automation
45
+ aos init --non-interactive
46
+
47
+ # Or install missing adapter packages after config generation
48
+ aos init --apply
49
+
37
50
  # Run a strategic deliberation
38
51
  aos run strategic-council --brief brief.md
39
52
 
@@ -58,7 +71,7 @@ aos validate
58
71
  | 0 | Success |
59
72
  | 1 | Uncaught runtime error |
60
73
  | 2 | Invalid input (unknown adapter, bad path, bad URL, missing adapter package) |
61
- | 3 | Profile tool-policy error (malformed `tools:` block, flag cannot widen profile) |
74
+ | 3 | Validation failure that requires user action (`aos init --non-interactive --adapter ...` selected adapter not ready, or profile tool-policy widening failure) |
62
75
 
63
76
  ## What It Does
64
77
 
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "aos-harness",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Agentic Orchestration System — assemble AI agents into deliberation and execution teams",
5
5
  "license": "MIT",
6
- "publishConfig": { "access": "public" },
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
7
9
  "repository": {
8
10
  "type": "git",
9
11
  "url": "https://github.com/aos-engineer/aos-harness.git"
@@ -36,24 +38,35 @@
36
38
  "test": "bun run src/index.ts validate"
37
39
  },
38
40
  "dependencies": {
39
- "@aos-harness/adapter-shared": "0.7.1",
40
- "@aos-harness/runtime": "0.7.1",
41
+ "@aos-harness/adapter-shared": "0.8.0",
42
+ "@aos-harness/runtime": "0.8.0",
43
+ "@clack/prompts": "^1.2.0",
41
44
  "@modelcontextprotocol/sdk": "^1.29.0",
42
- "js-yaml": "^4.1.0"
45
+ "js-yaml": "^4.1.0",
46
+ "yaml": "^2.8.3"
43
47
  },
44
48
  "peerDependencies": {
45
- "@aos-harness/claude-code-adapter": ">=0.6.0 <1.0.0",
46
- "@aos-harness/codex-adapter": ">=0.6.0 <1.0.0",
47
- "@aos-harness/gemini-adapter": ">=0.6.0 <1.0.0",
48
- "@aos-harness/pi-adapter": ">=0.6.0 <1.0.0"
49
+ "@aos-harness/claude-code-adapter": ">=0.8.0 <1.0.0",
50
+ "@aos-harness/codex-adapter": ">=0.8.0 <1.0.0",
51
+ "@aos-harness/gemini-adapter": ">=0.8.0 <1.0.0",
52
+ "@aos-harness/pi-adapter": ">=0.8.0 <1.0.0"
49
53
  },
50
54
  "peerDependenciesMeta": {
51
- "@aos-harness/claude-code-adapter": { "optional": true },
52
- "@aos-harness/codex-adapter": { "optional": true },
53
- "@aos-harness/gemini-adapter": { "optional": true },
54
- "@aos-harness/pi-adapter": { "optional": true }
55
+ "@aos-harness/claude-code-adapter": {
56
+ "optional": true
57
+ },
58
+ "@aos-harness/codex-adapter": {
59
+ "optional": true
60
+ },
61
+ "@aos-harness/gemini-adapter": {
62
+ "optional": true
63
+ },
64
+ "@aos-harness/pi-adapter": {
65
+ "optional": true
66
+ }
55
67
  },
56
68
  "devDependencies": {
69
+ "@types/bun": "^1.3.12",
57
70
  "@types/js-yaml": "^4.0.9",
58
71
  "typescript": "^5.4.0"
59
72
  }
@@ -0,0 +1,151 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import yaml from "js-yaml";
4
+ import { readAdapterConfig } from "./adapter-config";
5
+ import { ADAPTER_ALLOWLIST, type AdapterName, isValidAdapter } from "./utils";
6
+ import type { InitModels } from "./init-types";
7
+
8
+ export interface AosConfigV2 {
9
+ api_version?: string;
10
+ adapters?: {
11
+ enabled?: string[];
12
+ default?: string;
13
+ };
14
+ package_manager?: string;
15
+ models?: Partial<InitModels>;
16
+ editor?: string;
17
+ platform?: {
18
+ enabled?: boolean;
19
+ url?: string;
20
+ };
21
+ }
22
+
23
+ export interface AosConfigV1 {
24
+ adapter?: string;
25
+ models?: Partial<InitModels>;
26
+ editor?: string;
27
+ platform?: {
28
+ enabled?: boolean;
29
+ url?: string;
30
+ };
31
+ }
32
+
33
+ export type AosConfig = AosConfigV1 & AosConfigV2 & Record<string, unknown>;
34
+
35
+ export interface AdapterResolution {
36
+ adapter: AdapterName;
37
+ source: "flag" | "config-v2" | "config-v1" | "adapter-yaml" | "default";
38
+ }
39
+
40
+ export function readAosConfig(root: string): AosConfig | null {
41
+ const path = join(root, ".aos", "config.yaml");
42
+ if (!existsSync(path)) return null;
43
+ const parsed = yaml.load(readFileSync(path, "utf-8"), { schema: yaml.JSON_SCHEMA }) as AosConfig | null;
44
+ if (!parsed || typeof parsed !== "object") return null;
45
+ return parsed;
46
+ }
47
+
48
+ export function isAosConfigV2(config: AosConfig | null): boolean {
49
+ return !!config && config.api_version === "aos/config/v2";
50
+ }
51
+
52
+ export function parseAdapterList(value: unknown): AdapterName[] {
53
+ if (typeof value !== "string") return [];
54
+ const seen = new Set<AdapterName>();
55
+ for (const raw of value.split(",")) {
56
+ const candidate = raw.trim();
57
+ if (isValidAdapter(candidate)) seen.add(candidate);
58
+ }
59
+ return [...seen];
60
+ }
61
+
62
+ export function getEnabledAdaptersFromConfig(config: AosConfig | null): AdapterName[] {
63
+ if (!config) return [];
64
+ if (isAosConfigV2(config)) {
65
+ const enabled = Array.isArray(config.adapters?.enabled) ? config.adapters?.enabled : [];
66
+ return enabled.filter((value): value is AdapterName => isValidAdapter(value));
67
+ }
68
+ return typeof config.adapter === "string" && isValidAdapter(config.adapter) ? [config.adapter] : [];
69
+ }
70
+
71
+ export function getDefaultAdapterFromConfig(config: AosConfig | null): AdapterName | null {
72
+ if (!config) return null;
73
+ if (isAosConfigV2(config)) {
74
+ if (typeof config.adapters?.default === "string" && isValidAdapter(config.adapters.default)) {
75
+ return config.adapters.default;
76
+ }
77
+ const enabled = getEnabledAdaptersFromConfig(config);
78
+ return enabled[0] ?? null;
79
+ }
80
+ return typeof config.adapter === "string" && isValidAdapter(config.adapter) ? config.adapter : null;
81
+ }
82
+
83
+ export function resolveAdapterSelection(root: string, flagAdapter?: string | boolean): AdapterResolution {
84
+ if (typeof flagAdapter === "string" && isValidAdapter(flagAdapter)) {
85
+ return { adapter: flagAdapter, source: "flag" };
86
+ }
87
+
88
+ const aosConfig = readAosConfig(root);
89
+ if (aosConfig) {
90
+ const v2Default = isAosConfigV2(aosConfig) ? getDefaultAdapterFromConfig(aosConfig) : null;
91
+ if (v2Default) return { adapter: v2Default, source: "config-v2" };
92
+
93
+ if (typeof aosConfig.adapter === "string" && isValidAdapter(aosConfig.adapter)) {
94
+ return { adapter: aosConfig.adapter, source: "config-v1" };
95
+ }
96
+ }
97
+
98
+ const adapterConfig = readAdapterConfig(root);
99
+ if (typeof adapterConfig?.platform === "string" && isValidAdapter(adapterConfig.platform)) {
100
+ return { adapter: adapterConfig.platform, source: "adapter-yaml" };
101
+ }
102
+
103
+ return { adapter: "pi", source: "default" };
104
+ }
105
+
106
+ export function getSelectedAdaptersForInit(root: string, flagAdapter?: string | boolean): AdapterName[] {
107
+ const explicit = parseAdapterList(flagAdapter);
108
+ if (explicit.length > 0) return explicit;
109
+
110
+ const config = readAosConfig(root);
111
+ const fromConfig = getEnabledAdaptersFromConfig(config);
112
+ if (fromConfig.length > 0) return fromConfig;
113
+
114
+ const adapterConfig = readAdapterConfig(root);
115
+ if (typeof adapterConfig?.platform === "string" && isValidAdapter(adapterConfig.platform)) {
116
+ return [adapterConfig.platform];
117
+ }
118
+
119
+ return [];
120
+ }
121
+
122
+ export function getPlatformUrlFromConfig(root: string): string | null {
123
+ const config = readAosConfig(root);
124
+ if (!config?.platform?.enabled) return null;
125
+ return typeof config.platform.url === "string" ? config.platform.url : null;
126
+ }
127
+
128
+ export const DEFAULT_INIT_MODELS: InitModels = {
129
+ economy: "anthropic/claude-haiku-4-5",
130
+ standard: "anthropic/claude-sonnet-4-6",
131
+ premium: "anthropic/claude-opus-4-6",
132
+ };
133
+
134
+ export function getInitModels(root: string): InitModels {
135
+ const config = readAosConfig(root);
136
+ const models = config?.models ?? {};
137
+ return {
138
+ economy: typeof models.economy === "string" ? models.economy : DEFAULT_INIT_MODELS.economy,
139
+ standard: typeof models.standard === "string" ? models.standard : DEFAULT_INIT_MODELS.standard,
140
+ premium: typeof models.premium === "string" ? models.premium : DEFAULT_INIT_MODELS.premium,
141
+ };
142
+ }
143
+
144
+ export function getInitEditor(root: string): string {
145
+ const config = readAosConfig(root);
146
+ return typeof config?.editor === "string" ? config.editor : "code";
147
+ }
148
+
149
+ export function listKnownAdapters(): readonly AdapterName[] {
150
+ return ADAPTER_ALLOWLIST;
151
+ }
@@ -6,11 +6,28 @@ export interface BridgeHandlers {
6
6
  end: (params: { closing_message: string }) => Promise<unknown>;
7
7
  }
8
8
 
9
+ function parseListenTarget(target: string): { host: string; port: number } | { path: string } {
10
+ if (!target.startsWith("tcp://")) {
11
+ return { path: target };
12
+ }
13
+
14
+ const url = new URL(target);
15
+ const port = Number.parseInt(url.port, 10);
16
+ if (!url.hostname || Number.isNaN(port)) {
17
+ throw new Error(`Invalid bridge listen target: ${target}`);
18
+ }
19
+
20
+ return { host: url.hostname, port };
21
+ }
22
+
9
23
  export async function startBridgeServer(
10
24
  socketPath: string,
11
25
  handlers: BridgeHandlers,
12
26
  ): Promise<() => Promise<void>> {
13
- if (existsSync(socketPath)) unlinkSync(socketPath);
27
+ const listenTarget = parseListenTarget(socketPath);
28
+ const isUnixSocket = "path" in listenTarget;
29
+
30
+ if (isUnixSocket && existsSync(listenTarget.path)) unlinkSync(listenTarget.path);
14
31
 
15
32
  const open = new Set<Socket>();
16
33
 
@@ -43,12 +60,16 @@ export async function startBridgeServer(
43
60
 
44
61
  await new Promise<void>((resolve, reject) => {
45
62
  server.once("error", reject);
46
- server.listen(socketPath, () => resolve());
63
+ if ("path" in listenTarget) {
64
+ server.listen(listenTarget.path, () => resolve());
65
+ return;
66
+ }
67
+ server.listen(listenTarget.port, listenTarget.host, () => resolve());
47
68
  });
48
69
 
49
70
  return async () => {
50
71
  for (const s of open) s.destroy();
51
72
  await new Promise<void>((resolve) => server.close(() => resolve()));
52
- if (existsSync(socketPath)) unlinkSync(socketPath);
73
+ if (isUnixSocket && existsSync(listenTarget.path)) unlinkSync(listenTarget.path);
53
74
  };
54
75
  }
@@ -1,12 +1,20 @@
1
1
  /**
2
- * aos init — Initialize AOS configuration in the current project.
2
+ * aos init — Initialize or reconfigure AOS in the current project.
3
3
  */
4
4
 
5
5
  import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { dirname, join } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
+ import yaml from "js-yaml";
8
9
  import { c, type ParsedArgs } from "../colors";
9
- import { ADAPTER_ALLOWLIST, getHarnessRoot, getPackageCoreDir, isValidAdapter } from "../utils";
10
+ import { getHarnessRoot, getPackageCoreDir, isValidAdapter } from "../utils";
11
+ import { getInitEditor, getInitModels, getSelectedAdaptersForInit, parseAdapterList } from "../aos-config";
12
+ import { scanEnvironment } from "../env-scanner";
13
+ import { mergeConfig, generateMemoryYaml } from "../init-config-writer";
14
+ import { applyActions } from "../init-applier";
15
+ import type { AdapterName } from "../utils";
16
+ import type { ScanReport, WizardResult } from "../init-types";
17
+ import { runWizard } from "../init-wizard";
10
18
 
11
19
  function readCliVersion(): string {
12
20
  try {
@@ -19,39 +27,75 @@ function readCliVersion(): string {
19
27
  }
20
28
 
21
29
  const HELP = `
22
- ${c.bold("aos init")} — Initialize AOS in the current project
30
+ ${c.bold("aos init")} — Initialize or reconfigure AOS in the current project
23
31
 
24
32
  ${c.bold("USAGE")}
25
- aos init [--adapter <adapter>] [--force]
33
+ aos init [--adapter <adapter>] [--force] [--apply] [--non-interactive] [--from-yaml <path>]
26
34
 
27
35
  ${c.bold("OPTIONS")}
28
- --adapter <name> Adapter to use: pi (default), claude-code, gemini, codex
29
- --force Reinitialize even if AOS is already set up (overwrites existing core configs)
36
+ --adapter <name> Adapter to enable or validate: pi, claude-code, gemini, codex
37
+ --force Reinitialize config files and recopy core/ if needed
38
+ --apply Install missing AOS adapter packages after config generation
39
+ --non-interactive Scan-only mode. Writes .aos/scan.json and validates selected adapters if provided
40
+ --from-yaml <path> Read a prebuilt WizardResult YAML/JSON file and write config from it
30
41
 
31
42
  ${c.bold("DESCRIPTION")}
32
- Creates an .aos/ configuration directory in the current project with
33
- a default config.yaml. This is the first step to running AOS deliberations.
34
-
35
- ${c.bold("EXAMPLES")}
36
- aos init
37
- aos init --adapter pi
38
- aos init --adapter claude-code
43
+ Scans vendor CLI readiness, scans AOS adapter-package readiness, writes
44
+ .aos/config.yaml in v2 shape, and can optionally install missing adapter
45
+ packages. In non-interactive mode without a selected adapter, init is pure
46
+ scan/report and does not write config.
39
47
  `;
40
48
 
41
- function generateConfig(adapter: string): string {
42
- return `# AOS Harness Configuration
43
- # Generated by: aos init
44
- # Adapter: ${adapter}
49
+ function projectNameFromCwd(cwd: string): string {
50
+ return cwd.split("/").pop()?.toLowerCase().replace(/[^a-z0-9-]/g, "-") ?? "default";
51
+ }
52
+
53
+ function parseWizardResultFile(path: string): WizardResult {
54
+ const parsed = yaml.load(readFileSync(path, "utf-8"), { schema: yaml.JSON_SCHEMA }) as Partial<WizardResult> | null;
55
+ if (!parsed || typeof parsed !== "object") {
56
+ throw new Error("Malformed --from-yaml input: expected an object");
57
+ }
45
58
 
46
- adapter: ${adapter}
59
+ const enabledAdapters = Array.isArray(parsed.enabledAdapters)
60
+ ? parsed.enabledAdapters.filter((value): value is AdapterName => typeof value === "string" && isValidAdapter(value))
61
+ : [];
62
+ const defaultAdapter = typeof parsed.defaultAdapter === "string" && isValidAdapter(parsed.defaultAdapter)
63
+ ? parsed.defaultAdapter
64
+ : null;
47
65
 
48
- models:
49
- economy: anthropic/claude-haiku-4-5
50
- standard: anthropic/claude-sonnet-4-6
51
- premium: anthropic/claude-opus-4-6
66
+ if (enabledAdapters.length === 0 || !defaultAdapter || !enabledAdapters.includes(defaultAdapter)) {
67
+ throw new Error("Malformed --from-yaml input: enabledAdapters/defaultAdapter are invalid");
68
+ }
52
69
 
53
- editor: code
54
- `;
70
+ return {
71
+ enabledAdapters,
72
+ defaultAdapter,
73
+ memory: {
74
+ provider: parsed.memory?.provider === "mempalace" ? "mempalace" : "expertise",
75
+ },
76
+ models: parsed.models ?? getInitModels(process.cwd()),
77
+ editor: typeof parsed.editor === "string" ? parsed.editor : getInitEditor(process.cwd()),
78
+ actions: Array.isArray(parsed.actions) ? parsed.actions : [],
79
+ };
80
+ }
81
+
82
+ function writeScanReport(aosDir: string, scan: ScanReport): string {
83
+ mkdirSync(aosDir, { recursive: true });
84
+ const scanPath = join(aosDir, "scan.json");
85
+ writeFileSync(scanPath, JSON.stringify(scan, null, 2), "utf-8");
86
+ return scanPath;
87
+ }
88
+
89
+ function printAdapterInstallHints(version: string): void {
90
+ console.log(`${c.bold("Next step: install an adapter")}
91
+ Adapters augment the AI CLI you already use. Install the AOS adapter package that matches it:
92
+
93
+ Claude Code: ${c.cyan(`npm i -g @aos-harness/claude-code-adapter@${version}`)}
94
+ Gemini CLI: ${c.cyan(`npm i -g @aos-harness/gemini-adapter@${version}`)}
95
+ Codex CLI: ${c.cyan(`npm i -g @aos-harness/codex-adapter@${version}`)}
96
+ Pi: ${c.cyan(`npm i -g @aos-harness/pi-adapter@${version}`)}
97
+
98
+ ${c.dim("Install one (or more) that matches the vendor CLI you already have.")}`);
55
99
  }
56
100
 
57
101
  export async function initCommand(args: ParsedArgs): Promise<void> {
@@ -60,39 +104,59 @@ export async function initCommand(args: ParsedArgs): Promise<void> {
60
104
  return;
61
105
  }
62
106
 
63
- const adapter = (args.flags.adapter as string) || "pi";
64
-
65
- if (!isValidAdapter(adapter)) {
66
- console.error(c.red(`Invalid adapter: "${adapter}". Valid adapters: ${ADAPTER_ALLOWLIST.join(", ")}`));
67
- process.exit(1);
68
- }
69
-
70
107
  const cwd = process.cwd();
71
108
  const aosDir = join(cwd, ".aos");
72
109
  const configPath = join(aosDir, "config.yaml");
73
-
110
+ const memoryPath = join(aosDir, "memory.yaml");
74
111
  const force = !!args.flags.force;
75
-
76
- if (existsSync(join(cwd, "core", "agents")) && !force) {
77
- console.error(c.yellow("AOS project already exists in this directory."));
78
- console.log(c.dim(` Use "aos init --force" to reinitialize (overwrites existing core configs).`));
79
- process.exit(1);
112
+ const apply = !!args.flags.apply;
113
+ const isInteractive = !!(process.stdin.isTTY && process.stdout.isTTY);
114
+ const fromYamlPath = typeof args.flags["from-yaml"] === "string" ? args.flags["from-yaml"] : null;
115
+ const explicitAdapters = parseAdapterList(args.flags.adapter);
116
+
117
+ if (!isInteractive && !args.flags["non-interactive"] && !fromYamlPath) {
118
+ console.error('Pass --from-yaml=<path> or --non-interactive');
119
+ process.exit(2);
80
120
  }
81
121
 
82
- if (existsSync(configPath) && !force) {
83
- console.log(c.yellow("AOS is already initialized in this project."));
84
- console.log(c.dim(` Config: ${configPath}`));
85
- console.log(c.dim(` Use "aos init --force" to reinitialize.`));
122
+ const scan = await scanEnvironment({ cwd });
123
+ const scanPath = writeScanReport(aosDir, scan);
124
+
125
+ if (args.flags["non-interactive"] && !fromYamlPath) {
126
+ const selected = explicitAdapters.length > 0 ? explicitAdapters : getSelectedAdaptersForInit(cwd, args.flags.adapter);
127
+ if (selected.length === 0) {
128
+ console.log(`${c.green("Scan complete.")} Report: ${c.cyan(scanPath)}`);
129
+ return;
130
+ }
131
+
132
+ const notReady = selected.filter((adapter) => scan.adapters[adapter].status !== "ready");
133
+ if (notReady.length > 0) {
134
+ console.error(c.red(`Selected adapters are not ready: ${notReady.join(", ")}`));
135
+ for (const adapter of notReady) {
136
+ console.error(c.dim(` ${adapter}: ${scan.adapters[adapter].statusHint}`));
137
+ }
138
+ process.exit(3);
139
+ }
140
+
141
+ console.log(`${c.green("Selected adapters are ready.")} Report: ${c.cyan(scanPath)}`);
86
142
  return;
87
143
  }
88
144
 
89
- // Create .aos directory
145
+ let wizardResult: WizardResult;
146
+ if (fromYamlPath) {
147
+ wizardResult = parseWizardResultFile(fromYamlPath);
148
+ } else {
149
+ wizardResult = await runWizard(scan, cwd, args.flags.adapter);
150
+ }
151
+
90
152
  mkdirSync(aosDir, { recursive: true });
153
+ writeFileSync(configPath, mergeConfig(cwd, wizardResult, scan.packageManager), "utf-8");
91
154
 
92
- // Write config
93
- writeFileSync(configPath, generateConfig(adapter), "utf-8");
155
+ const projectName = projectNameFromCwd(cwd);
156
+ if (!existsSync(memoryPath) || force) {
157
+ writeFileSync(memoryPath, generateMemoryYaml(projectName, wizardResult.memory.provider), "utf-8");
158
+ }
94
159
 
95
- // Copy core configs if not already present (or --force)
96
160
  const destCore = join(cwd, "core");
97
161
  if (!existsSync(destCore) || force) {
98
162
  const sourceCore = getPackageCoreDir() ?? join(getHarnessRoot(), "core");
@@ -101,82 +165,31 @@ export async function initCommand(args: ParsedArgs): Promise<void> {
101
165
  console.log(c.green(" Copied core configs (agents, profiles, domains, workflows, skills)"));
102
166
  } else {
103
167
  console.log(c.yellow(" Warning: Could not find core configs to copy."));
104
- console.log(c.dim(` You may need to manually copy the core/ directory from the AOS Harness repository.`));
105
168
  }
106
169
  }
107
170
 
108
- const projectName = cwd.split("/").pop()?.toLowerCase().replace(/[^a-z0-9-]/g, "-") ?? "default";
109
-
110
- const memoryYaml = `# AOS Memory Configuration
111
- # Generated by: aos init
112
- api_version: aos/memory/v1
113
-
114
- # Memory provider: "mempalace" (recommended) or "expertise" (built-in fallback)
115
- provider: expertise
116
-
117
- # MemPalace settings (used when provider: mempalace)
118
- # mempalace:
119
- # palace_path: ~/.mempalace/palace
120
- # project_wing: ${projectName}
121
- # wake_layers: [L0, L1]
122
- # auto_hall: true
123
- # max_wake_tokens: 1200
124
- # max_drawer_tokens: 500
125
-
126
- # Built-in expertise settings (used when provider: expertise)
127
- expertise:
128
- max_lines: 200
129
- scope: per-project
130
-
131
- # Orchestrator memory behavior
132
- orchestrator:
133
- remember_prompt: session_end
134
- recall_gate: true
135
- max_recall_per_session: 10
136
- `;
137
-
138
- const memoryPath = join(aosDir, "memory.yaml");
139
- if (!existsSync(memoryPath) || force) {
140
- writeFileSync(memoryPath, memoryYaml);
141
- console.log(c.dim(` Memory config: ${memoryPath}`));
142
- }
143
-
144
171
  console.log(`
145
172
  ${c.green("AOS initialized successfully!")}
146
173
 
147
174
  ${c.bold("Configuration")}
148
175
  Directory: ${c.cyan(aosDir)}
149
176
  Config: ${c.cyan(configPath)}
150
- Adapter: ${c.cyan(adapter)}
151
-
152
- ${c.bold("Getting Started")}
153
- 1. Write a brief describing your strategic problem
154
- ${c.dim("See core/briefs/sample-product-decision/brief.md for an example")}
155
-
156
- 2. Run a deliberation session:
157
- ${c.cyan("aos run strategic-council --brief path/to/your/brief.md")}
158
-
159
- 3. Or list available profiles and agents:
160
- ${c.cyan("aos list")}
161
-
162
- ${c.bold("Customization")}
163
- Create custom agents: ${c.cyan("aos create agent <name>")}
164
- Create custom profiles: ${c.cyan("aos create profile <name>")}
165
- Create domain packs: ${c.cyan("aos create domain <name>")}
166
- Validate everything: ${c.cyan("aos validate")}
177
+ Adapter: ${c.cyan(wizardResult.defaultAdapter)}
178
+ Scan: ${c.cyan(scanPath)}
167
179
  `);
168
180
 
169
- // Adapter-install guidance. Starting in 0.6.0 the CLI no longer bundles
170
- // adapter source — users must install the adapter(s) they want to use.
171
- const v = readCliVersion();
172
- console.log(`${c.bold("Next step: install an adapter")}
173
- Adapters live as separate npm packages. Install the one(s) you'll use:
181
+ printAdapterInstallHints(readCliVersion());
174
182
 
175
- Claude Code: ${c.cyan(`npm i -g @aos-harness/claude-code-adapter@${v}`)}
176
- Gemini CLI: ${c.cyan(`npm i -g @aos-harness/gemini-adapter@${v}`)}
177
- Codex CLI: ${c.cyan(`npm i -g @aos-harness/codex-adapter@${v}`)}
178
- Pi (pi.dev): ${c.cyan(`npm i -g @aos-harness/pi-adapter@${v}`)}
179
-
180
- ${c.dim("Pick one (or more). Then run `aos run`.")}
181
- `);
183
+ if (apply) {
184
+ const result = await applyActions(wizardResult.actions);
185
+ if (!result.ok) {
186
+ console.error(c.red(`Some installs failed: ${result.failures.join(", ")}`));
187
+ process.exit(1);
188
+ }
189
+ } else {
190
+ const installable = wizardResult.actions.filter((action) => action.type === "install-adapter");
191
+ if (installable.length > 0) {
192
+ console.log(c.dim(`Run "aos init --apply" to install missing adapter packages with ${scan.packageManager}.`));
193
+ }
194
+ }
182
195
  }
@@ -10,6 +10,7 @@ import type { TranscriptEntry } from "@aos-harness/runtime/types";
10
10
  import { runAdapterSession } from "../adapter-session";
11
11
  import { readAdapterConfig } from "../adapter-config";
12
12
  import { buildToolPolicy, type ToolPolicy } from "@aos-harness/adapter-shared";
13
+ import { getPlatformUrlFromConfig, resolveAdapterSelection } from "../aos-config";
13
14
 
14
15
  function createEventBuffer(platformUrl: string, sessionId: string) {
15
16
  const buffer: TranscriptEntry[] = [];
@@ -273,8 +274,8 @@ export async function runCommand(args: ParsedArgs): Promise<void> {
273
274
  let workflowSection = "";
274
275
  if (isExecutionProfile && workflowConfig) {
275
276
  const stepSummary = workflowConfig.steps
276
- .map((s: { id: string; name: string; action: string; review_gate?: boolean }) =>
277
- ` ${s.id.padEnd(20)} ${s.name.padEnd(30)} ${s.action}${s.review_gate ? " [gate]" : ""}`
277
+ .map((s) =>
278
+ ` ${s.id.padEnd(20)} ${(s.name ?? s.id).padEnd(30)} ${s.action}${s.review_gate ? " [gate]" : ""}`
278
279
  )
279
280
  .join("\n");
280
281
  const gateCount = workflowConfig.gates?.length || 0;
@@ -348,20 +349,13 @@ ${c.bold(`AOS ${sessionType} Session`)}
348
349
  Output: ${c.cyan(deliberationDir)}
349
350
  `);
350
351
 
351
- // Determine adapter: --adapter flag > .aos/config.yaml > default "pi"
352
+ // Determine adapter with shared precedence so init/run agree:
353
+ // --adapter > config v2 > config v1 > .aos/adapter.yaml > default "pi"
352
354
  let platformUrl = (args.flags["platform-url"] as string) || null;
353
- const aosConfigPath = join(process.cwd(), ".aos", "config.yaml");
354
- let adapter = "pi";
355
- if (existsSync(aosConfigPath)) {
356
- const yaml = await import("js-yaml");
357
- const configText = await Bun.file(aosConfigPath).text();
358
- const config = yaml.load(configText, { schema: yaml.JSON_SCHEMA }) as Record<string, unknown>;
359
- adapter = (config.adapter as string) || "pi";
360
- if (!platformUrl && config?.platform && (config.platform as Record<string, unknown>)?.enabled && (config.platform as Record<string, unknown>)?.url) {
361
- platformUrl = (config.platform as Record<string, unknown>).url as string;
362
- }
355
+ const { adapter } = resolveAdapterSelection(process.cwd(), args.flags["adapter"]);
356
+ if (!platformUrl) {
357
+ platformUrl = getPlatformUrlFromConfig(process.cwd());
363
358
  }
364
- if (args.flags["adapter"]) adapter = args.flags["adapter"] as string;
365
359
 
366
360
  // Validate platform URL (spec D5). Fires for both --platform-url flag
367
361
  // and .aos/config.yaml platform.url after both sources are merged.