@cleocode/cleo-os 2026.4.12 → 2026.4.16

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/cli.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CleoOS launcher — the batteries-included agentic development environment.
4
+ *
5
+ * Wraps Pi's `main()` entry point with the cleo-cant-bridge pre-loaded
6
+ * as an extension. Pi stays upstream (ULTRAPLAN L1). This is a thin
7
+ * launcher that injects CleoOS extensions into Pi's CLI argument list.
8
+ *
9
+ * Usage: `cleoos [pi-args...]` — launches Pi with CANT bridge extension.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
package/dist/cli.js ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CleoOS launcher — the batteries-included agentic development environment.
4
+ *
5
+ * Wraps Pi's `main()` entry point with the cleo-cant-bridge pre-loaded
6
+ * as an extension. Pi stays upstream (ULTRAPLAN L1). This is a thin
7
+ * launcher that injects CleoOS extensions into Pi's CLI argument list.
8
+ *
9
+ * Usage: `cleoos [pi-args...]` — launches Pi with CANT bridge extension.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import { existsSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import { resolveCleoOsPaths } from './xdg.js';
16
+ /**
17
+ * Collect CleoOS extension paths that exist on disk.
18
+ *
19
+ * Resolves the CANT bridge extension from the XDG data directory.
20
+ * Only returns paths for extensions that actually exist on the filesystem.
21
+ *
22
+ * @returns Array of absolute extension file paths.
23
+ */
24
+ function collectExtensionPaths() {
25
+ const paths = resolveCleoOsPaths();
26
+ const extensions = [];
27
+ const bridgePath = join(paths.extensions, 'cleo-cant-bridge.js');
28
+ if (existsSync(bridgePath)) {
29
+ extensions.push(bridgePath);
30
+ }
31
+ return extensions;
32
+ }
33
+ /**
34
+ * Build the argument list for Pi's `main()`, injecting CleoOS extensions.
35
+ *
36
+ * Takes the user's CLI arguments (everything after `cleoos`) and prepends
37
+ * `--extension <path>` flags for each discovered CleoOS extension.
38
+ *
39
+ * @param userArgs - Arguments passed to `cleoos` by the user.
40
+ * @param extensionPaths - Resolved extension paths to inject.
41
+ * @returns Combined argument array for Pi's `main()`.
42
+ */
43
+ function buildArgs(userArgs, extensionPaths) {
44
+ const extensionFlags = extensionPaths.flatMap((p) => ['--extension', p]);
45
+ return [...extensionFlags, ...userArgs];
46
+ }
47
+ /**
48
+ * Entry point for the `cleoos` binary.
49
+ *
50
+ * Dynamically imports Pi's coding agent (peerDependency), resolves CleoOS
51
+ * extension paths, and delegates to Pi's `main()` with the bridge extension
52
+ * injected into the argument list.
53
+ *
54
+ * Exits with code 1 if Pi is not installed, providing install instructions.
55
+ */
56
+ async function main() {
57
+ // Dynamically import Pi — it's a peerDependency, may not be installed
58
+ let piMain;
59
+ try {
60
+ const pi = await import('@mariozechner/pi-coding-agent');
61
+ piMain = pi.main;
62
+ }
63
+ catch {
64
+ console.error('CleoOS requires Pi Coding Agent to be installed.\n' +
65
+ 'Run: npm install -g @mariozechner/pi-coding-agent\n' +
66
+ 'Then try again: cleoos');
67
+ process.exit(1);
68
+ }
69
+ const extensionPaths = collectExtensionPaths();
70
+ const args = buildArgs(process.argv.slice(2), extensionPaths);
71
+ await piMain(args);
72
+ }
73
+ main().catch((err) => {
74
+ console.error('CleoOS fatal:', err instanceof Error ? err.message : String(err));
75
+ process.exit(1);
76
+ });
77
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;;;GAOG;AACH,SAAS,qBAAqB;IAC5B,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC;IACjE,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,SAAS,CAAC,QAAkB,EAAE,cAAwB;IAC7D,MAAM,cAAc,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,cAAc,EAAE,GAAG,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,IAAI;IACjB,sEAAsE;IACtE,IAAI,MAAyC,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAC;QACzD,MAAM,GAAG,EAAE,CAAC,IAAI,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CACX,oDAAoD;YAClD,qDAAqD;YACrD,wBAAwB,CAC3B,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,cAAc,GAAG,qBAAqB,EAAE,CAAC;IAC/C,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;IAE9D,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACjF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * CleoOS keystore — Pi API key management with XDG-compliant storage.
3
+ *
4
+ * Wraps Pi's `FileAuthStorageBackend` with a CleoOS-specific XDG path so
5
+ * that credentials are persisted at `~/.config/cleo/auth/auth.json` (the
6
+ * `auth` sub-path of the CleoOS XDG config directory) rather than Pi's
7
+ * default location.
8
+ *
9
+ * This ensures:
10
+ * - Credentials survive `cleoos` reinstalls (XDG is outside the package).
11
+ * - Multiple CleoOS installations on the same host share credentials.
12
+ * - The auth file lives under the XDG Config spec (`~/.config/cleo/`).
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ import { FileAuthStorageBackend } from '@mariozechner/pi-coding-agent';
17
+ /**
18
+ * Resolve a `FileAuthStorageBackend` configured to use the CleoOS XDG
19
+ * auth directory (`~/.config/cleo/auth/auth.json`).
20
+ *
21
+ * The directory is NOT created here — that is handled by the postinstall
22
+ * script so that directory creation is a one-time operation.
23
+ *
24
+ * @returns A `FileAuthStorageBackend` pointed at the XDG-compliant path.
25
+ */
26
+ export declare function resolveKeystore(): FileAuthStorageBackend;
27
+ //# sourceMappingURL=keystore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keystore.d.ts","sourceRoot":"","sources":["../src/keystore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAMvE;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,sBAAsB,CAIxD"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * CleoOS keystore — Pi API key management with XDG-compliant storage.
3
+ *
4
+ * Wraps Pi's `FileAuthStorageBackend` with a CleoOS-specific XDG path so
5
+ * that credentials are persisted at `~/.config/cleo/auth/auth.json` (the
6
+ * `auth` sub-path of the CleoOS XDG config directory) rather than Pi's
7
+ * default location.
8
+ *
9
+ * This ensures:
10
+ * - Credentials survive `cleoos` reinstalls (XDG is outside the package).
11
+ * - Multiple CleoOS installations on the same host share credentials.
12
+ * - The auth file lives under the XDG Config spec (`~/.config/cleo/`).
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ import { join } from 'node:path';
17
+ import { FileAuthStorageBackend } from '@mariozechner/pi-coding-agent';
18
+ import { resolveCleoOsPaths } from './xdg.js';
19
+ /** Default auth file name inside the keystore directory. */
20
+ const AUTH_FILENAME = 'auth.json';
21
+ /**
22
+ * Resolve a `FileAuthStorageBackend` configured to use the CleoOS XDG
23
+ * auth directory (`~/.config/cleo/auth/auth.json`).
24
+ *
25
+ * The directory is NOT created here — that is handled by the postinstall
26
+ * script so that directory creation is a one-time operation.
27
+ *
28
+ * @returns A `FileAuthStorageBackend` pointed at the XDG-compliant path.
29
+ */
30
+ export function resolveKeystore() {
31
+ const paths = resolveCleoOsPaths();
32
+ const authFilePath = join(paths.auth, AUTH_FILENAME);
33
+ return new FileAuthStorageBackend(authFilePath);
34
+ }
35
+ //# sourceMappingURL=keystore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keystore.js","sourceRoot":"","sources":["../src/keystore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,4DAA4D;AAC5D,MAAM,aAAa,GAAG,WAAW,CAAC;AAElC;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IACrD,OAAO,IAAI,sBAAsB,CAAC,YAAY,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * CleoOS postinstall — scaffolds the global XDG hub and deploys extensions.
3
+ *
4
+ * Runs automatically after `npm install -g @cleocode/cleo-os`.
5
+ * Creates an XDG-compliant directory structure, copies the compiled CANT
6
+ * bridge extension to the extensions directory, and optionally invokes
7
+ * `cleo skills install` for any bundled CleoOS skills.
8
+ *
9
+ * Behaviour:
10
+ * - Skips silently during workspace/dev installs (non-global).
11
+ * - All directory creation is idempotent (no-op if directory exists).
12
+ * - All file copies are idempotent (only copies if target is missing).
13
+ * - Skill install is best-effort; failures are logged but not fatal.
14
+ * - Missing `@mariozechner/pi-coding-agent` is handled gracefully.
15
+ *
16
+ * This source compiles to `bin/postinstall.js` via a dedicated tsconfig
17
+ * (see `tsconfig.postinstall.json`). The `postinstall` script in
18
+ * `package.json` references the compiled output at `bin/postinstall.js`.
19
+ *
20
+ * @packageDocumentation
21
+ */
22
+ export {};
23
+ //# sourceMappingURL=postinstall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postinstall.d.ts","sourceRoot":"","sources":["../src/postinstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG"}
@@ -0,0 +1,208 @@
1
+ /**
2
+ * CleoOS postinstall — scaffolds the global XDG hub and deploys extensions.
3
+ *
4
+ * Runs automatically after `npm install -g @cleocode/cleo-os`.
5
+ * Creates an XDG-compliant directory structure, copies the compiled CANT
6
+ * bridge extension to the extensions directory, and optionally invokes
7
+ * `cleo skills install` for any bundled CleoOS skills.
8
+ *
9
+ * Behaviour:
10
+ * - Skips silently during workspace/dev installs (non-global).
11
+ * - All directory creation is idempotent (no-op if directory exists).
12
+ * - All file copies are idempotent (only copies if target is missing).
13
+ * - Skill install is best-effort; failures are logged but not fatal.
14
+ * - Missing `@mariozechner/pi-coding-agent` is handled gracefully.
15
+ *
16
+ * This source compiles to `bin/postinstall.js` via a dedicated tsconfig
17
+ * (see `tsconfig.postinstall.json`). The `postinstall` script in
18
+ * `package.json` references the compiled output at `bin/postinstall.js`.
19
+ *
20
+ * @packageDocumentation
21
+ */
22
+ import { execFileSync } from 'node:child_process';
23
+ import { cpSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
24
+ import { homedir } from 'node:os';
25
+ import { dirname, join, resolve } from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+ const __filename = fileURLToPath(import.meta.url);
28
+ const __dirname = dirname(__filename);
29
+ // ---------------------------------------------------------------------------
30
+ // XDG path resolution (inline copy — avoids importing from dist/ which may
31
+ // not exist when this script runs for the first time)
32
+ // ---------------------------------------------------------------------------
33
+ /**
34
+ * Inline XDG path resolution that mirrors `src/xdg.ts`.
35
+ *
36
+ * Uses an inline copy here so the postinstall script can run before
37
+ * the compiled `dist/` tree is available on a fresh install.
38
+ *
39
+ * @returns Resolved CleoOS XDG directory paths.
40
+ */
41
+ function resolveCleoOsPaths() {
42
+ const home = homedir();
43
+ const xdgData = process.env['XDG_DATA_HOME'] ?? join(home, '.local', 'share');
44
+ const xdgConfig = process.env['XDG_CONFIG_HOME'] ?? join(home, '.config');
45
+ const data = join(xdgData, 'cleo');
46
+ const config = join(xdgConfig, 'cleo');
47
+ return {
48
+ data,
49
+ config,
50
+ agentDir: data,
51
+ extensions: join(data, 'extensions'),
52
+ cant: join(data, 'cant'),
53
+ auth: join(config, 'auth'),
54
+ };
55
+ }
56
+ // ---------------------------------------------------------------------------
57
+ // Global install detection
58
+ // ---------------------------------------------------------------------------
59
+ /**
60
+ * Detect whether this is a global npm / pnpm install.
61
+ *
62
+ * Uses four heuristics in priority order:
63
+ * 1. `npm_config_global=true` env var (set by npm/pnpm for global installs)
64
+ * 2. Package path contains `lib/node_modules/` (npm global pattern)
65
+ * 3. Package path starts with `npm_config_prefix` (npm prefix-based check)
66
+ * 4. Presence of `pnpm-workspace.yaml` two levels up (workspace = dev)
67
+ *
68
+ * @returns `true` if the install appears to be a global install.
69
+ */
70
+ function isGlobalInstall() {
71
+ const pkgRoot = resolve(__dirname, '..');
72
+ // Signal 1: npm_config_global env var (set by npm during global installs)
73
+ if (process.env['npm_config_global'] === 'true')
74
+ return true;
75
+ // Signal 2: path contains a global node_modules (npm, pnpm, yarn)
76
+ if (/[/\\]lib[/\\]node_modules[/\\]/.test(pkgRoot))
77
+ return true;
78
+ // Signal 3: npm_config_prefix matches the package path
79
+ const prefix = process.env['npm_config_prefix'];
80
+ if (prefix !== undefined && pkgRoot.startsWith(prefix))
81
+ return true;
82
+ // Signal 4: inside a pnpm workspace — definitely not global
83
+ const workspaceMarker = join(pkgRoot, '..', '..', 'pnpm-workspace.yaml');
84
+ if (existsSync(workspaceMarker))
85
+ return false;
86
+ return false;
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // Directory scaffolding
90
+ // ---------------------------------------------------------------------------
91
+ /**
92
+ * Idempotently create a directory if it does not already exist.
93
+ *
94
+ * @param dir - Absolute path to the directory to create.
95
+ */
96
+ function ensureDir(dir) {
97
+ if (!existsSync(dir)) {
98
+ mkdirSync(dir, { recursive: true });
99
+ process.stdout.write(`CleoOS: created ${dir}\n`);
100
+ }
101
+ }
102
+ // ---------------------------------------------------------------------------
103
+ // Extension deployment
104
+ // ---------------------------------------------------------------------------
105
+ /**
106
+ * Copy a compiled extension to the XDG extensions directory.
107
+ *
108
+ * Only copies if the target does not already exist (idempotent). The
109
+ * source is the compiled `.js` file in the package's `extensions/` folder.
110
+ *
111
+ * @param extensionName - Filename without the `.js` extension.
112
+ * @param pkgRoot - Absolute path to the installed package root.
113
+ * @param extensionsDir - Absolute path to the XDG extensions directory.
114
+ */
115
+ function deployExtension(extensionName, pkgRoot, extensionsDir) {
116
+ const src = join(pkgRoot, 'extensions', `${extensionName}.js`);
117
+ const dest = join(extensionsDir, `${extensionName}.js`);
118
+ if (!existsSync(src)) {
119
+ process.stdout.write(`CleoOS: skipping ${extensionName}.js (source not found at ${src})\n`);
120
+ return;
121
+ }
122
+ if (existsSync(dest)) {
123
+ // Already deployed — idempotent, skip.
124
+ return;
125
+ }
126
+ cpSync(src, dest, { force: false });
127
+ process.stdout.write(`CleoOS: deployed ${extensionName}.js to ${dest}\n`);
128
+ }
129
+ // ---------------------------------------------------------------------------
130
+ // Default CANT file scaffolding
131
+ // ---------------------------------------------------------------------------
132
+ /**
133
+ * Write a default `model-routing.cant` stub to the XDG CANT directory if
134
+ * no `.cant` files are present. This gives the user a starting point for
135
+ * CANT declarations without overwriting any existing work.
136
+ *
137
+ * @param cantDir - Absolute path to the XDG CANT directory.
138
+ */
139
+ function scaffoldDefaultCant(cantDir) {
140
+ const modelRoutingPath = join(cantDir, 'model-routing.cant');
141
+ if (existsSync(modelRoutingPath))
142
+ return;
143
+ const stub = [
144
+ '# CleoOS default model-routing.cant',
145
+ '# Declare agents, teams, and routing rules here.',
146
+ '# See: https://github.com/kryptobaseddev/cleo/blob/main/docs/cant-dsl.md',
147
+ '',
148
+ ].join('\n');
149
+ try {
150
+ writeFileSync(modelRoutingPath, stub, 'utf-8');
151
+ process.stdout.write(`CleoOS: created default ${modelRoutingPath}\n`);
152
+ }
153
+ catch {
154
+ // Best-effort: non-fatal.
155
+ }
156
+ }
157
+ // ---------------------------------------------------------------------------
158
+ // Skill installation
159
+ // ---------------------------------------------------------------------------
160
+ /**
161
+ * Invoke `cleo skills install` via `execFileSync` to register the CleoOS
162
+ * bundled skills with the project. This is best-effort — if `cleo` is not
163
+ * on PATH or the command fails, we log and continue.
164
+ *
165
+ * Uses `execFileSync` (not `exec`) to prevent shell injection: the command
166
+ * and arguments are passed as separate parameters so no shell is spawned.
167
+ */
168
+ function installSkills() {
169
+ try {
170
+ execFileSync('cleo', ['skills', 'install'], { stdio: 'inherit' });
171
+ process.stdout.write('CleoOS: skills install complete\n');
172
+ }
173
+ catch {
174
+ // cleo may not be installed or skills may already be up to date.
175
+ process.stdout.write('CleoOS: skipping skills install (cleo not found or already installed)\n');
176
+ }
177
+ }
178
+ // ---------------------------------------------------------------------------
179
+ // Main
180
+ // ---------------------------------------------------------------------------
181
+ /**
182
+ * Entry point for the CleoOS postinstall script.
183
+ *
184
+ * Orchestrates directory scaffolding, extension deployment, CANT stub
185
+ * creation, and optional skill installation. All operations are idempotent.
186
+ */
187
+ function main() {
188
+ if (!isGlobalInstall()) {
189
+ process.stdout.write('CleoOS: skipping postinstall (not global install)\n');
190
+ return;
191
+ }
192
+ const paths = resolveCleoOsPaths();
193
+ const pkgRoot = resolve(__dirname, '..');
194
+ // 1. Scaffold XDG directories
195
+ for (const dir of [paths.data, paths.config, paths.extensions, paths.cant, paths.auth]) {
196
+ ensureDir(dir);
197
+ }
198
+ // 2. Deploy compiled extensions
199
+ deployExtension('cleo-cant-bridge', pkgRoot, paths.extensions);
200
+ deployExtension('cleo-chatroom', pkgRoot, paths.extensions);
201
+ // 3. Write default CANT stub (only if file does not exist)
202
+ scaffoldDefaultCant(paths.cant);
203
+ // 4. Install CleoOS skills (best-effort)
204
+ installSkills();
205
+ process.stdout.write('CleoOS: postinstall complete\n');
206
+ }
207
+ main();
208
+ //# sourceMappingURL=postinstall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postinstall.js","sourceRoot":"","sources":["../src/postinstall.ts"],"names":[],"mappings":"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"}
package/dist/xdg.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * XDG-compliant path resolution for CleoOS.
3
+ *
4
+ * Resolves:
5
+ * - Data: $XDG_DATA_HOME/cleo/ or ~/.local/share/cleo/
6
+ * - Config: $XDG_CONFIG_HOME/cleo/ or ~/.config/cleo/
7
+ * - Agent dir: same as data root (Pi's agentDir equivalent)
8
+ * - Extensions: <data>/extensions/
9
+ * - CANT source: <data>/cant/ (global tier)
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ /** Resolved CleoOS filesystem paths following XDG Base Directory Specification. */
14
+ export interface CleoOsPaths {
15
+ /** XDG data home: ~/.local/share/cleo/ */
16
+ data: string;
17
+ /** XDG config home: ~/.config/cleo/ */
18
+ config: string;
19
+ /** Pi agent directory (= data root) */
20
+ agentDir: string;
21
+ /** Extensions directory: <data>/extensions/ */
22
+ extensions: string;
23
+ /** Global CANT source: <data>/cant/ */
24
+ cant: string;
25
+ /** Auth/keystore directory: <config>/auth/ */
26
+ auth: string;
27
+ }
28
+ /**
29
+ * Resolve CleoOS filesystem paths using XDG Base Directory Specification.
30
+ *
31
+ * Respects `XDG_DATA_HOME` and `XDG_CONFIG_HOME` environment variables
32
+ * when set, falling back to the XDG defaults (`~/.local/share/` and
33
+ * `~/.config/` respectively).
34
+ *
35
+ * @returns Resolved paths for all CleoOS directories.
36
+ */
37
+ export declare function resolveCleoOsPaths(): CleoOsPaths;
38
+ //# sourceMappingURL=xdg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xdg.d.ts","sourceRoot":"","sources":["../src/xdg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,IAAI,WAAW,CAgBhD"}
package/dist/xdg.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * XDG-compliant path resolution for CleoOS.
3
+ *
4
+ * Resolves:
5
+ * - Data: $XDG_DATA_HOME/cleo/ or ~/.local/share/cleo/
6
+ * - Config: $XDG_CONFIG_HOME/cleo/ or ~/.config/cleo/
7
+ * - Agent dir: same as data root (Pi's agentDir equivalent)
8
+ * - Extensions: <data>/extensions/
9
+ * - CANT source: <data>/cant/ (global tier)
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import { homedir } from 'node:os';
14
+ import { join } from 'node:path';
15
+ /**
16
+ * Resolve CleoOS filesystem paths using XDG Base Directory Specification.
17
+ *
18
+ * Respects `XDG_DATA_HOME` and `XDG_CONFIG_HOME` environment variables
19
+ * when set, falling back to the XDG defaults (`~/.local/share/` and
20
+ * `~/.config/` respectively).
21
+ *
22
+ * @returns Resolved paths for all CleoOS directories.
23
+ */
24
+ export function resolveCleoOsPaths() {
25
+ const home = homedir();
26
+ const xdgData = process.env['XDG_DATA_HOME'] ?? join(home, '.local', 'share');
27
+ const xdgConfig = process.env['XDG_CONFIG_HOME'] ?? join(home, '.config');
28
+ const data = join(xdgData, 'cleo');
29
+ const config = join(xdgConfig, 'cleo');
30
+ return {
31
+ data,
32
+ config,
33
+ agentDir: data,
34
+ extensions: join(data, 'extensions'),
35
+ cant: join(data, 'cant'),
36
+ auth: join(config, 'auth'),
37
+ };
38
+ }
39
+ //# sourceMappingURL=xdg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xdg.js","sourceRoot":"","sources":["../src/xdg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAkBjC;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9E,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAE1E,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAEvC,OAAO;QACL,IAAI;QACJ,MAAM;QACN,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC;QACpC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;KAC3B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,598 @@
1
+ /**
2
+ * CleoOS CANT bridge — Wave 2 Pi extension.
3
+ *
4
+ * CANONICAL LOCATION: `packages/cleo-os/extensions/cleo-cant-bridge.ts`
5
+ *
6
+ * This file was copied from
7
+ * `packages/cleo/templates/cleoos-hub/pi-extensions/cleo-cant-bridge.ts`
8
+ * (T393). The template path is kept for reference but this file is the
9
+ * authoritative source. A future cleanup wave (post-T381) should remove
10
+ * the template copy once all consumers have migrated.
11
+ *
12
+ * Installed to: $XDG_DATA_HOME/cleo/extensions/cleo-cant-bridge.js
13
+ * Loaded by: Pi via `--extension <path>` injected by CleoOS cli.ts
14
+ *
15
+ * This bridge discovers `.cant` files in the project's `.cleo/cant/`
16
+ * directory at session start, compiles them via `@cleocode/cant`'s
17
+ * `compileBundle()`, and appends the compiled declarations to Pi's
18
+ * system prompt on `before_agent_start`. This gives the LLM awareness
19
+ * of all declared agents, teams, and tools without hand-authored
20
+ * protocol text.
21
+ *
22
+ * Wave 2 scope:
23
+ * - Scans project tier only: `<cwd>/.cleo/cant/` (recursive)
24
+ * - Three-tier resolution (global, user, project) is Wave 5
25
+ * - Prompt strategy: APPEND (per ULTRAPLAN L6, never replace)
26
+ *
27
+ * Wave 8 additions (T420):
28
+ * - validate-on-load mental-model injection
29
+ * - When the spawned agent's CANT definition has a `mentalModel` block,
30
+ * fetches prior mental-model observations via memoryFind and injects
31
+ * them into the Pi system prompt with VALIDATE_ON_LOAD_PREAMBLE.
32
+ * - Exports `VALIDATE_ON_LOAD_PREAMBLE` and `buildMentalModelInjection`
33
+ * for testability (T421).
34
+ *
35
+ * Requirements:
36
+ * - `@cleocode/cant` must be installed (provides `compileBundle`)
37
+ * - Pi coding agent runtime (`@mariozechner/pi-coding-agent`)
38
+ *
39
+ * Guardrails:
40
+ * - Best-effort: if `@cleocode/cant` is not installed or `.cleo/cant`
41
+ * does not exist, the bridge is a no-op. NEVER crash Pi.
42
+ * - NO top-level await; all work happens inside event handlers.
43
+ * - APPEND to system prompt, never replace.
44
+ *
45
+ * @packageDocumentation
46
+ */
47
+
48
+ import { existsSync, readdirSync } from "node:fs";
49
+ import { join } from "node:path";
50
+ import type {
51
+ ExtensionAPI,
52
+ ExtensionContext,
53
+ } from "@mariozechner/pi-coding-agent";
54
+
55
+ // ============================================================================
56
+ // T420: validate-on-load constants and pure helpers
57
+ // ============================================================================
58
+
59
+ /**
60
+ * Preamble text injected into the Pi system prompt when an agent has a
61
+ * `mental_model:` CANT block. The agent MUST re-evaluate each observation
62
+ * against the current project state before acting.
63
+ *
64
+ * Exported so empirical tests (T421) can assert on its presence.
65
+ */
66
+ export const VALIDATE_ON_LOAD_PREAMBLE =
67
+ "===== MENTAL MODEL (validate-on-load) =====\n" +
68
+ "These are your prior observations, patterns, and learnings for this project.\n" +
69
+ "Before acting, you MUST re-evaluate each entry against current project state.\n" +
70
+ "If an entry is stale, note it and proceed with fresh understanding.";
71
+
72
+ /** Minimal observation shape returned by memoryFind / searchBrainCompact. */
73
+ export interface MentalModelObservation {
74
+ id: string;
75
+ type: string;
76
+ title: string;
77
+ date?: string;
78
+ }
79
+
80
+ /**
81
+ * Build the validate-on-load mental-model injection string.
82
+ *
83
+ * Pure function — no I/O, safe to call in tests without a real DB.
84
+ *
85
+ * @param agentName - Name of the spawned agent (used in the header line).
86
+ * @param observations - Prior mental-model observations to list.
87
+ * @returns System-prompt block containing the preamble and numbered observations,
88
+ * or an empty string when `observations` is empty.
89
+ */
90
+ export function buildMentalModelInjection(
91
+ agentName: string,
92
+ observations: MentalModelObservation[],
93
+ ): string {
94
+ if (observations.length === 0) return "";
95
+
96
+ const lines: string[] = [
97
+ "",
98
+ `// Agent: ${agentName}`,
99
+ VALIDATE_ON_LOAD_PREAMBLE,
100
+ "",
101
+ ];
102
+
103
+ for (let i = 0; i < observations.length; i++) {
104
+ const obs = observations[i];
105
+ const datePart = obs.date ? ` [${obs.date}]` : "";
106
+ lines.push(`${i + 1}. [${obs.id}] (${obs.type})${datePart}: ${obs.title}`);
107
+ }
108
+
109
+ lines.push("===== END MENTAL MODEL =====");
110
+
111
+ return lines.join("\n");
112
+ }
113
+
114
+ // ============================================================================
115
+ // T424: Path-ACL helpers (pure, no external deps)
116
+ // ============================================================================
117
+
118
+ /**
119
+ * Path-scoped file permissions shape expected in an agentDef at runtime.
120
+ *
121
+ * Mirrors `PathPermissions` from `@cleocode/cant` (T423).
122
+ * Kept inline here to avoid a direct runtime import in the Pi extension context.
123
+ *
124
+ * @task T424
125
+ */
126
+ interface AgentFilePermissions {
127
+ /** Glob patterns the agent may write to. Empty array = no writes allowed. */
128
+ write?: string[];
129
+ /** Glob patterns the agent may read from. */
130
+ read?: string[];
131
+ /** Glob patterns the agent may delete. */
132
+ delete?: string[];
133
+ }
134
+
135
+ /**
136
+ * Convert a glob pattern to a RegExp for path matching.
137
+ *
138
+ * Supports the subset of glob syntax used in CANT file permissions:
139
+ * - `**` matches any path segment sequence (including none)
140
+ * - `*` matches any characters within a single path segment
141
+ * - `?` matches a single character
142
+ * - All other characters are treated as literals
143
+ *
144
+ * @param glob - The glob pattern string.
145
+ * @returns A RegExp that tests absolute or relative file paths.
146
+ */
147
+ function globToRegExp(glob: string): RegExp {
148
+ // Escape special regex characters except our glob specials
149
+ let regexStr = "";
150
+ let i = 0;
151
+ while (i < glob.length) {
152
+ const char = glob[i];
153
+ if (char === "*" && glob[i + 1] === "*") {
154
+ // ** matches everything including path separators
155
+ regexStr += ".*";
156
+ i += 2;
157
+ // Skip optional trailing slash after **
158
+ if (glob[i] === "/") i++;
159
+ } else if (char === "*") {
160
+ // * matches anything except path separator
161
+ regexStr += "[^/]*";
162
+ i++;
163
+ } else if (char === "?") {
164
+ regexStr += "[^/]";
165
+ i++;
166
+ } else if (/[.+^${}()|[\]\\]/.test(char)) {
167
+ regexStr += "\\" + char;
168
+ i++;
169
+ } else {
170
+ regexStr += char;
171
+ i++;
172
+ }
173
+ }
174
+ return new RegExp("^" + regexStr + "$");
175
+ }
176
+
177
+ /**
178
+ * Test whether a file path matches any of the provided glob patterns.
179
+ *
180
+ * Normalises the path to use forward slashes. Returns `false` immediately
181
+ * when `globs` is an empty array (default-deny for empty write lists).
182
+ *
183
+ * @param filePath - The file path to test (absolute or relative).
184
+ * @param globs - The glob patterns to test against.
185
+ * @returns `true` if `filePath` matches at least one glob pattern.
186
+ */
187
+ function matchesAnyGlob(filePath: string, globs: string[]): boolean {
188
+ if (globs.length === 0) return false;
189
+ // Normalise separators; strip leading slash for relative matching
190
+ const normalized = filePath.replace(/\\/g, "/").replace(/^\//, "");
191
+ for (const glob of globs) {
192
+ if (globToRegExp(glob).test(normalized)) return true;
193
+ }
194
+ return false;
195
+ }
196
+
197
+ /**
198
+ * Attempt to extract the target file path from a Pi tool_call event.
199
+ *
200
+ * Handles the three writable tool shapes:
201
+ * - `Edit`: `{ input: { file_path: string } }` or `{ filePath: string }`
202
+ * - `Write`: `{ input: { file_path: string } }` or `{ filePath: string }`
203
+ * - `Bash`: best-effort scan of the command string for write destinations
204
+ *
205
+ * Returns `null` when the path cannot be determined (allow-by-default for Bash
206
+ * when the destination is ambiguous).
207
+ *
208
+ * @param toolName - The tool being invoked ("Edit", "Write", or "Bash").
209
+ * @param toolInput - The raw tool input object.
210
+ * @returns The extracted file path, or `null` if not determinable.
211
+ */
212
+ function extractTargetPath(
213
+ toolName: string,
214
+ toolInput: Record<string, unknown> | undefined,
215
+ ): string | null {
216
+ if (!toolInput) return null;
217
+
218
+ if (toolName === "Edit" || toolName === "Write") {
219
+ // Pi uses snake_case in the actual tool call input
220
+ if (typeof toolInput["file_path"] === "string") return toolInput["file_path"];
221
+ // camelCase fallback (bridge convention)
222
+ if (typeof toolInput["filePath"] === "string") return toolInput["filePath"];
223
+ // path fallback
224
+ if (typeof toolInput["path"] === "string") return toolInput["path"];
225
+ return null;
226
+ }
227
+
228
+ if (toolName === "Bash") {
229
+ const cmd = typeof toolInput["command"] === "string" ? toolInput["command"] : null;
230
+ if (!cmd) return null;
231
+
232
+ // Detect common write patterns: redirection, tee, cp/mv destination
233
+ // Best-effort: return the first detected destination path.
234
+ // If ambiguous, return null (allow-by-default for Bash).
235
+ const redirectMatch = cmd.match(/>\s*["']?([^\s"';&|]+)/);
236
+ if (redirectMatch?.[1]) return redirectMatch[1];
237
+
238
+ const teeMatch = cmd.match(/\btee\s+(?:-a\s+)?["']?([^\s"';&|]+)/);
239
+ if (teeMatch?.[1]) return teeMatch[1];
240
+
241
+ // cp/mv destination is the last argument — very heuristic
242
+ const cpMvMatch = cmd.match(/\b(?:cp|mv)\s+\S+\s+["']?([^\s"';&|]+)/);
243
+ if (cpMvMatch?.[1]) return cpMvMatch[1];
244
+
245
+ return null; // Cannot determine — allow (workers self-report)
246
+ }
247
+
248
+ return null;
249
+ }
250
+
251
+ // ============================================================================
252
+ // Internal state
253
+ // ============================================================================
254
+
255
+ /** Cached system prompt addendum from the last session_start compilation. */
256
+ let bundlePrompt: string | null = null;
257
+
258
+ /** Diagnostic summary cached for /cant:bundle-info. */
259
+ let lastDiagnosticSummary: string | null = null;
260
+
261
+ /**
262
+ * Recursively discover `.cant` files in a directory.
263
+ *
264
+ * @param dir - The directory to scan recursively.
265
+ * @returns An array of absolute paths to `.cant` files found.
266
+ */
267
+ function discoverCantFiles(dir: string): string[] {
268
+ try {
269
+ const entries = readdirSync(dir, { recursive: true, withFileTypes: true });
270
+ const files: string[] = [];
271
+ for (const entry of entries) {
272
+ if (entry.isFile() && entry.name.endsWith(".cant")) {
273
+ // Node 24+ recursive readdir returns entries with parentPath
274
+ const parent = (entry as unknown as { parentPath?: string }).parentPath ?? dir;
275
+ files.push(join(parent, entry.name));
276
+ }
277
+ }
278
+ return files;
279
+ } catch {
280
+ return [];
281
+ }
282
+ }
283
+
284
+ // ============================================================================
285
+ // T420: mental-model injection helper (async, calls memoryFind)
286
+ // ============================================================================
287
+
288
+ /**
289
+ * Fetch prior mental-model observations for an agent and build the
290
+ * validate-on-load injection block.
291
+ *
292
+ * Called in `before_agent_start` when the agent has a `mentalModel` CANT block.
293
+ * Best-effort: returns empty string on any failure so Pi is never blocked.
294
+ *
295
+ * @param agentName - Name of the spawned agent.
296
+ * @param projectRoot - Project root directory for brain.db access.
297
+ * @returns The validate-on-load system-prompt block, or "" on failure/empty.
298
+ */
299
+ async function fetchMentalModelInjection(
300
+ agentName: string,
301
+ projectRoot: string,
302
+ ): Promise<string> {
303
+ try {
304
+ // Lazy import: @cleocode/core may not be present in all environments.
305
+ // memoryFind is the engine-compat wrapper (T418) that accepts `agent`.
306
+ const coreModule = (await import("@cleocode/core")) as {
307
+ memoryFind?: (
308
+ params: {
309
+ query: string;
310
+ agent?: string;
311
+ limit?: number;
312
+ tables?: string[];
313
+ },
314
+ projectRoot?: string,
315
+ ) => Promise<{
316
+ success: boolean;
317
+ data?: {
318
+ results?: MentalModelObservation[];
319
+ };
320
+ }>;
321
+ };
322
+
323
+ if (typeof coreModule.memoryFind !== "function") return "";
324
+
325
+ // Fetch the 10 most recent mental-model observations for this agent.
326
+ // Use tables filter to avoid decisions/patterns/learnings which are
327
+ // not agent-scoped in the current schema.
328
+ const result = await coreModule.memoryFind(
329
+ {
330
+ query: agentName,
331
+ agent: agentName,
332
+ limit: 10,
333
+ tables: ["observations"],
334
+ },
335
+ projectRoot,
336
+ );
337
+
338
+ if (!result.success || !result.data?.results?.length) return "";
339
+
340
+ return buildMentalModelInjection(agentName, result.data.results);
341
+ } catch {
342
+ // Best-effort — never crash Pi
343
+ return "";
344
+ }
345
+ }
346
+
347
+ // ============================================================================
348
+ // Pi extension factory
349
+ // ============================================================================
350
+
351
+ /**
352
+ * Pi extension factory for the CleoOS CANT bridge.
353
+ *
354
+ * Registers event handlers for `session_start` (compile `.cant` files)
355
+ * and `before_agent_start` (append compiled bundle + mental-model injection
356
+ * to system prompt). Also registers a `/cant:bundle-info` command for
357
+ * introspection.
358
+ *
359
+ * @param pi - The Pi extension API instance.
360
+ */
361
+ export default function (pi: ExtensionAPI): void {
362
+ // session_start: discover and compile .cant files from the project tier
363
+ pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => {
364
+ bundlePrompt = null;
365
+ lastDiagnosticSummary = null;
366
+
367
+ try {
368
+ const cantDir = join(ctx.cwd, ".cleo", "cant");
369
+ if (!existsSync(cantDir)) return;
370
+
371
+ const files = discoverCantFiles(cantDir);
372
+ if (files.length === 0) return;
373
+
374
+ // Dynamic import: @cleocode/cant may not be installed in all environments
375
+ const cantModule = (await import("@cleocode/cant")) as {
376
+ compileBundle: (paths: string[]) => Promise<{
377
+ renderSystemPrompt: () => string;
378
+ diagnostics: Array<{ severity: string; message: string; sourcePath: string }>;
379
+ agents: Array<{ name: string }>;
380
+ teams: Array<{ name: string }>;
381
+ tools: Array<{ name: string }>;
382
+ valid: boolean;
383
+ }>;
384
+ };
385
+
386
+ const bundle = await cantModule.compileBundle(files);
387
+ const prompt = bundle.renderSystemPrompt();
388
+
389
+ if (prompt.length > 0) {
390
+ bundlePrompt = prompt;
391
+ }
392
+
393
+ // Build diagnostic summary
394
+ const errorDiags = bundle.diagnostics.filter((d) => d.severity === "error");
395
+ const warnDiags = bundle.diagnostics.filter((d) => d.severity === "warning");
396
+ lastDiagnosticSummary = [
397
+ `Files: ${files.length}`,
398
+ `Agents: ${bundle.agents.length}`,
399
+ `Teams: ${bundle.teams.length}`,
400
+ `Tools: ${bundle.tools.length}`,
401
+ `Valid: ${bundle.valid}`,
402
+ `Errors: ${errorDiags.length}`,
403
+ `Warnings: ${warnDiags.length}`,
404
+ ].join(", ");
405
+
406
+ // Notify on errors
407
+ if (errorDiags.length > 0 && ctx.hasUI) {
408
+ ctx.ui.notify(
409
+ `CleoOS CANT bridge: ${errorDiags.length} validation error(s) in .cleo/cant/`,
410
+ "warning",
411
+ );
412
+ }
413
+
414
+ // Success notification
415
+ if (ctx.hasUI) {
416
+ ctx.ui.setStatus(
417
+ "cleo-cant-bridge",
418
+ `CANT: ${bundle.agents.length} agent(s), ${files.length} file(s)`,
419
+ );
420
+ }
421
+ } catch (err: unknown) {
422
+ // Best-effort: never crash Pi
423
+ const message = err instanceof Error ? err.message : String(err);
424
+ if (ctx.hasUI) {
425
+ ctx.ui.notify(`CleoOS CANT bridge: ${message}`, "warning");
426
+ }
427
+ }
428
+ });
429
+
430
+ // before_agent_start: APPEND compiled bundle prompt + mental-model injection
431
+ // to system prompt (per ULTRAPLAN L6, never replace)
432
+ pi.on(
433
+ "before_agent_start",
434
+ async (
435
+ event: {
436
+ systemPrompt?: string;
437
+ agentName?: string;
438
+ /** T420: agent CANT definition, if resolved by Pi runtime. */
439
+ agentDef?: {
440
+ /** mentalModel block presence signals validate-on-load injection. */
441
+ mentalModel?: unknown;
442
+ };
443
+ /** Project root injected by Pi when available. */
444
+ projectRoot?: string;
445
+ },
446
+ ctx?: ExtensionContext,
447
+ ) => {
448
+ const existingPrompt = event.systemPrompt ?? "";
449
+ let appendix = "";
450
+
451
+ // APPEND CANT bundle prompt
452
+ if (bundlePrompt) {
453
+ appendix += "\n\n" + bundlePrompt;
454
+ }
455
+
456
+ // T420: validate-on-load mental-model injection.
457
+ // Inject when the agent has a `mentalModel` CANT block.
458
+ const agentName = event.agentName;
459
+ const hasMentalModel =
460
+ agentName !== undefined &&
461
+ agentName !== "" &&
462
+ event.agentDef?.mentalModel !== undefined;
463
+
464
+ if (hasMentalModel && agentName) {
465
+ // Resolve project root: prefer explicit field, fall back to ctx.cwd
466
+ const projectRoot = event.projectRoot ?? ctx?.cwd ?? "";
467
+ if (projectRoot) {
468
+ const mentalModelBlock = await fetchMentalModelInjection(agentName, projectRoot);
469
+ if (mentalModelBlock) {
470
+ appendix += mentalModelBlock;
471
+ }
472
+ }
473
+ }
474
+
475
+ if (!appendix) return {};
476
+
477
+ return {
478
+ systemPrompt: existingPrompt + appendix,
479
+ };
480
+ },
481
+ );
482
+
483
+ // tool_call: ULTRAPLAN §10.3 — Lead agents MUST NOT execute Edit/Write/Bash.
484
+ // T424: Worker agents with declared file permissions are restricted to their
485
+ // declared write globs. Leads dispatch; workers execute within scope.
486
+ // Fires on every Pi tool_call event.
487
+ // The before_agent_start handler (T420 validate-on-load) is NOT touched here.
488
+ pi.on(
489
+ "tool_call",
490
+ async (event: {
491
+ /** CANT agent definition resolved by Pi at spawn time, if available. */
492
+ agentDef?: {
493
+ /** Tier role declared in the .cant file (e.g. "lead", "worker", "orchestrator"). */
494
+ role?: string;
495
+ /** Path-scoped file permissions declared in the .cant file (T423). */
496
+ filePermissions?: AgentFilePermissions;
497
+ /** Agent name for diagnostic messages. */
498
+ name?: string;
499
+ };
500
+ /** The tool name being invoked (e.g. "Edit", "Write", "Bash"). */
501
+ toolName?: string;
502
+ /** The raw tool input object (contains file_path for Edit/Write, command for Bash). */
503
+ toolInput?: Record<string, unknown>;
504
+ }) => {
505
+ const agentDef = event.agentDef;
506
+ // No agentDef = no restrictions (hook is a no-op).
507
+ if (!agentDef) return {};
508
+
509
+ const toolName = event.toolName ?? "";
510
+ const BLOCKED_TOOLS = ["Edit", "Write", "Bash"];
511
+
512
+ // ── W7b: Lead blocking ─────────────────────────────────────────────
513
+ // Only restrict agents whose CANT role is explicitly "lead".
514
+ // Non-lead roles (worker, orchestrator, undefined) pass this gate.
515
+ if (agentDef.role !== "lead") {
516
+ // Fall through to the T424 worker path ACL check below.
517
+ } else {
518
+ // Lead role: block Edit/Write/Bash entirely.
519
+ if (!BLOCKED_TOOLS.includes(toolName)) return {};
520
+
521
+ // Reject the tool call with a LAFS error envelope.
522
+ return {
523
+ rejected: true,
524
+ error: {
525
+ code: 70,
526
+ codeName: "E_LEAD_TOOL_BLOCKED",
527
+ message: `Lead agents cannot execute ${toolName} — dispatch to a worker instead`,
528
+ fix: "Use the delegate tool to spawn a worker agent for this work",
529
+ },
530
+ };
531
+ }
532
+
533
+ // ── T424: Worker path ACL ──────────────────────────────────────────
534
+ // Workers with declared file permissions can only write inside their
535
+ // declared globs. Applies to Edit, Write, and Bash (best-effort).
536
+ if (
537
+ agentDef.role === "worker" &&
538
+ agentDef.filePermissions !== undefined &&
539
+ BLOCKED_TOOLS.includes(toolName)
540
+ ) {
541
+ const writeGlobs = agentDef.filePermissions.write;
542
+ // `undefined` write field = no declared write ACL = allow through.
543
+ // Empty array [] = explicit no-writes = default-deny.
544
+ if (writeGlobs !== undefined) {
545
+ const targetPath = extractTargetPath(toolName, event.toolInput);
546
+ if (targetPath !== null && !matchesAnyGlob(targetPath, writeGlobs)) {
547
+ const agentName = agentDef.name ?? "worker";
548
+ const scopeList =
549
+ writeGlobs.length > 0 ? writeGlobs.join(", ") : "(none — this worker is read-only)";
550
+ return {
551
+ rejected: true,
552
+ error: {
553
+ code: 71,
554
+ codeName: "E_WORKER_PATH_ACL_VIOLATION",
555
+ message: `Worker ${agentName} is not allowed to write to ${targetPath}`,
556
+ fix:
557
+ `This worker can only write inside: ${scopeList}. ` +
558
+ "Either update the worker's permissions.files.write glob in " +
559
+ ".cleo/teams.cant, or dispatch to a different worker with matching scope.",
560
+ },
561
+ };
562
+ }
563
+ }
564
+ }
565
+
566
+ return {};
567
+ },
568
+ );
569
+
570
+ // /cant:bundle-info — introspection command
571
+ pi.registerCommand("cant:bundle-info", {
572
+ description: "Show the state of the CANT bundle compiled at session start",
573
+ handler: async (
574
+ _args: string,
575
+ ctx: ExtensionContext & { hasUI: boolean; signal?: AbortSignal },
576
+ ) => {
577
+ const content = lastDiagnosticSummary
578
+ ? `CANT Bundle: ${lastDiagnosticSummary}`
579
+ : "CANT Bundle: no .cant files compiled (check .cleo/cant/ directory)";
580
+ pi.sendMessage(
581
+ { customType: "cleo-cant-bundle-info", content, display: true },
582
+ { triggerTurn: false },
583
+ );
584
+ if (ctx.hasUI) {
585
+ ctx.ui.notify(
586
+ lastDiagnosticSummary ? "CANT bundle loaded" : "No CANT bundle",
587
+ "info",
588
+ );
589
+ }
590
+ },
591
+ });
592
+
593
+ // session_shutdown: clear cached state
594
+ pi.on("session_shutdown", async () => {
595
+ bundlePrompt = null;
596
+ lastDiagnosticSummary = null;
597
+ });
598
+ }
@@ -36,8 +36,14 @@ import { Type } from "@sinclair/typebox";
36
36
  // Message model
37
37
  // ---------------------------------------------------------------------------
38
38
 
39
+ /**
40
+ * Tier role of an agent in the 3-tier hierarchy (ULTRAPLAN §10).
41
+ * Used to apply distinct TUI styling per tier.
42
+ */
43
+ export type AgentTierRole = "orchestrator" | "lead" | "worker";
44
+
39
45
  /** A single inter-agent chat message. */
40
- interface ChatMessage {
46
+ export interface ChatMessage {
41
47
  /** ISO-8601 timestamp of when the message was created. */
42
48
  timestamp: string;
43
49
  /** Name of the sending agent. */
@@ -48,6 +54,14 @@ interface ChatMessage {
48
54
  channel: "send_to_lead" | "broadcast_to_team" | "report_to_orchestrator" | "query_peer";
49
55
  /** The message text. */
50
56
  text: string;
57
+ /**
58
+ * Optional tier role of the sending agent.
59
+ *
60
+ * When present, the TUI row is prefixed and (if ANSI is available)
61
+ * coloured by tier: orchestrator = green ([O]), lead = yellow ([L]),
62
+ * worker = blue ([W]). Defaults to "worker" when absent.
63
+ */
64
+ role?: AgentTierRole;
51
65
  }
52
66
 
53
67
  // ---------------------------------------------------------------------------
@@ -85,15 +99,40 @@ function recordMessage(msg: ChatMessage): void {
85
99
  }
86
100
  }
87
101
 
102
+ /**
103
+ * Return the single-character tier prefix for a chat message row.
104
+ *
105
+ * - `[O]` orchestrator (green in ANSI-capable terminals)
106
+ * - `[L]` lead (yellow)
107
+ * - `[W]` worker (blue, default)
108
+ *
109
+ * @param role - The sending agent's tier role, or `undefined` to default to worker.
110
+ * @returns The three-character prefix string.
111
+ */
112
+ export function tierPrefix(role: AgentTierRole | undefined): string {
113
+ switch (role) {
114
+ case "orchestrator":
115
+ return "[O]";
116
+ case "lead":
117
+ return "[L]";
118
+ default:
119
+ return "[W]";
120
+ }
121
+ }
122
+
88
123
  /**
89
124
  * Format a chat message for TUI display.
90
125
  *
126
+ * Each row is prefixed with a tier indicator ([O]/[L]/[W]) so orchestrator,
127
+ * lead, and worker traffic is visually distinct in the chat panel (ULTRAPLAN §13).
128
+ *
91
129
  * @param msg - The message to format.
92
130
  * @returns A single-line string representation.
93
131
  */
94
- function formatMessage(msg: ChatMessage): string {
132
+ export function formatMessage(msg: ChatMessage): string {
95
133
  const time = msg.timestamp.slice(11, 19);
96
- return `[${time}] ${msg.from} -> ${msg.to}: ${msg.text}`;
134
+ const prefix = tierPrefix(msg.role);
135
+ return `${prefix} [${time}] ${msg.from} -> ${msg.to}: ${msg.text}`;
97
136
  }
98
137
 
99
138
  /**
@@ -123,12 +162,26 @@ const SendToLeadParams = Type.Object({
123
162
  message: Type.String({ description: "Message to send to your team lead" }),
124
163
  from: Type.String({ description: "Your agent name" }),
125
164
  lead: Type.String({ description: "Name of the lead agent" }),
165
+ role: Type.Optional(
166
+ Type.Union([
167
+ Type.Literal("orchestrator"),
168
+ Type.Literal("lead"),
169
+ Type.Literal("worker"),
170
+ ], { description: "Tier role of the sending agent for TUI row styling" }),
171
+ ),
126
172
  });
127
173
 
128
174
  const BroadcastToTeamParams = Type.Object({
129
175
  message: Type.String({ description: "Message to broadcast to the team" }),
130
176
  from: Type.String({ description: "Your agent name (lead)" }),
131
177
  group: Type.String({ description: "Team group name (e.g. 'backend')" }),
178
+ role: Type.Optional(
179
+ Type.Union([
180
+ Type.Literal("orchestrator"),
181
+ Type.Literal("lead"),
182
+ Type.Literal("worker"),
183
+ ], { description: "Tier role of the sending agent for TUI row styling" }),
184
+ ),
132
185
  });
133
186
 
134
187
  const ReportToOrchestratorParams = Type.Object({
@@ -137,12 +190,26 @@ const ReportToOrchestratorParams = Type.Object({
137
190
  orchestrator: Type.String({
138
191
  description: "Name of the orchestrator agent",
139
192
  }),
193
+ role: Type.Optional(
194
+ Type.Union([
195
+ Type.Literal("orchestrator"),
196
+ Type.Literal("lead"),
197
+ Type.Literal("worker"),
198
+ ], { description: "Tier role of the sending agent for TUI row styling" }),
199
+ ),
140
200
  });
141
201
 
142
202
  const QueryPeerParams = Type.Object({
143
203
  message: Type.String({ description: "Query for your peer worker" }),
144
204
  from: Type.String({ description: "Your agent name" }),
145
205
  peer: Type.String({ description: "Name of the peer worker to query" }),
206
+ role: Type.Optional(
207
+ Type.Union([
208
+ Type.Literal("orchestrator"),
209
+ Type.Literal("lead"),
210
+ Type.Literal("worker"),
211
+ ], { description: "Tier role of the sending agent for TUI row styling" }),
212
+ ),
146
213
  });
147
214
 
148
215
  // ---------------------------------------------------------------------------
@@ -194,7 +261,7 @@ export default function (pi: ExtensionAPI): void {
194
261
  parameters: SendToLeadParams,
195
262
  async execute(
196
263
  _id: string,
197
- params: { message: string; from: string; lead: string },
264
+ params: { message: string; from: string; lead: string; role?: AgentTierRole },
198
265
  _signal: AbortSignal,
199
266
  _onUpdate: (text: string) => void,
200
267
  ctx: ExtensionContext,
@@ -205,6 +272,7 @@ export default function (pi: ExtensionAPI): void {
205
272
  to: params.lead,
206
273
  channel: "send_to_lead",
207
274
  text: params.message,
275
+ role: params.role,
208
276
  };
209
277
  recordMessage(msg);
210
278
  renderWidget(ctx);
@@ -231,7 +299,7 @@ export default function (pi: ExtensionAPI): void {
231
299
  parameters: BroadcastToTeamParams,
232
300
  async execute(
233
301
  _id: string,
234
- params: { message: string; from: string; group: string },
302
+ params: { message: string; from: string; group: string; role?: AgentTierRole },
235
303
  _signal: AbortSignal,
236
304
  _onUpdate: (text: string) => void,
237
305
  ctx: ExtensionContext,
@@ -242,6 +310,7 @@ export default function (pi: ExtensionAPI): void {
242
310
  to: `team:${params.group}`,
243
311
  channel: "broadcast_to_team",
244
312
  text: params.message,
313
+ role: params.role,
245
314
  };
246
315
  recordMessage(msg);
247
316
  renderWidget(ctx);
@@ -268,7 +337,7 @@ export default function (pi: ExtensionAPI): void {
268
337
  parameters: ReportToOrchestratorParams,
269
338
  async execute(
270
339
  _id: string,
271
- params: { message: string; from: string; orchestrator: string },
340
+ params: { message: string; from: string; orchestrator: string; role?: AgentTierRole },
272
341
  _signal: AbortSignal,
273
342
  _onUpdate: (text: string) => void,
274
343
  ctx: ExtensionContext,
@@ -279,6 +348,7 @@ export default function (pi: ExtensionAPI): void {
279
348
  to: params.orchestrator,
280
349
  channel: "report_to_orchestrator",
281
350
  text: params.message,
351
+ role: params.role,
282
352
  };
283
353
  recordMessage(msg);
284
354
  renderWidget(ctx);
@@ -305,7 +375,7 @@ export default function (pi: ExtensionAPI): void {
305
375
  parameters: QueryPeerParams,
306
376
  async execute(
307
377
  _id: string,
308
- params: { message: string; from: string; peer: string },
378
+ params: { message: string; from: string; peer: string; role?: AgentTierRole },
309
379
  _signal: AbortSignal,
310
380
  _onUpdate: (text: string) => void,
311
381
  ctx: ExtensionContext,
@@ -316,6 +386,7 @@ export default function (pi: ExtensionAPI): void {
316
386
  to: params.peer,
317
387
  channel: "query_peer",
318
388
  text: params.message,
389
+ role: params.role,
319
390
  };
320
391
  recordMessage(msg);
321
392
  renderWidget(ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/cleo-os",
3
- "version": "2026.4.12",
3
+ "version": "2026.4.16",
4
4
  "description": "CleoOS — the batteries-included agentic development environment wrapping Pi",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",
@@ -8,8 +8,8 @@
8
8
  "cleoos": "dist/cli.js"
9
9
  },
10
10
  "dependencies": {
11
- "@cleocode/cleo": "2026.4.12",
12
- "@cleocode/cant": "2026.4.12"
11
+ "@cleocode/cant": "2026.4.16",
12
+ "@cleocode/cleo": "2026.4.16"
13
13
  },
14
14
  "peerDependencies": {
15
15
  "@mariozechner/pi-coding-agent": ">=0.60.0"
@@ -20,8 +20,8 @@
20
20
  }
21
21
  },
22
22
  "devDependencies": {
23
- "typescript": "^5.9.0",
24
- "vitest": "^4.1.0"
23
+ "typescript": "^6.0.2",
24
+ "vitest": "^4.1.4"
25
25
  },
26
26
  "engines": {
27
27
  "node": ">=24.0.0"
@@ -41,8 +41,11 @@
41
41
  "bin"
42
42
  ],
43
43
  "scripts": {
44
- "build": "tsc",
45
- "typecheck": "tsc --noEmit",
44
+ "build": "tsc && tsc -p tsconfig.extensions.json && tsc -p tsconfig.postinstall.json",
45
+ "build:src": "tsc",
46
+ "build:extensions": "tsc -p tsconfig.extensions.json",
47
+ "build:postinstall": "tsc -p tsconfig.postinstall.json",
48
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.extensions.json --noEmit && tsc -p tsconfig.postinstall.json --noEmit",
46
49
  "test": "vitest run",
47
50
  "postinstall": "node bin/postinstall.js"
48
51
  }