@pleri/olam-cli 0.1.174 → 0.1.180
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 +19 -0
- package/bin/olam.cjs +22 -0
- package/dist/commands/flywheel/index.d.ts.map +1 -1
- package/dist/commands/flywheel/index.js +4 -0
- package/dist/commands/flywheel/index.js.map +1 -1
- package/dist/commands/flywheel/install-sessionstart-hook.d.ts +64 -0
- package/dist/commands/flywheel/install-sessionstart-hook.d.ts.map +1 -0
- package/dist/commands/flywheel/install-sessionstart-hook.js +197 -0
- package/dist/commands/flywheel/install-sessionstart-hook.js.map +1 -0
- package/dist/commands/flywheel/session-start.d.ts +26 -0
- package/dist/commands/flywheel/session-start.d.ts.map +1 -0
- package/dist/commands/flywheel/session-start.js +119 -0
- package/dist/commands/flywheel/session-start.js.map +1 -0
- package/dist/commands/host-cp.d.ts +0 -3
- package/dist/commands/host-cp.d.ts.map +1 -1
- package/dist/commands/host-cp.js +27 -2
- package/dist/commands/host-cp.js.map +1 -1
- package/dist/commands/yolo.d.ts +99 -0
- package/dist/commands/yolo.d.ts.map +1 -0
- package/dist/commands/yolo.js +377 -0
- package/dist/commands/yolo.js.map +1 -0
- package/dist/image-digests.json +8 -8
- package/dist/index.js +808 -207
- package/dist/index.js.map +1 -1
- package/dist/lib/auth-remote.d.ts +0 -17
- package/dist/lib/auth-remote.d.ts.map +1 -1
- package/dist/lib/auth-remote.js +6 -16
- package/dist/lib/auth-remote.js.map +1 -1
- package/dist/mcp-server.js +26 -0
- package/hermes-bundle/version.json +1 -1
- package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
- package/host-cp/src/bootstrap-selective.mjs +56 -0
- package/host-cp/src/plan-chat-service.mjs +51 -0
- package/host-cp/src/redirect.mjs +152 -0
- package/host-cp/src/resolver.mjs +121 -0
- package/host-cp/src/server.mjs +57 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,6 +121,25 @@ olam --help
|
|
|
121
121
|
olam <command> --help
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
## Agentic coding — yolo dispatch
|
|
125
|
+
|
|
126
|
+
`olam yolo` dispatches a parallel Claude Code session (`--dangerously-skip-permissions`)
|
|
127
|
+
in a new tmux window backed by an isolated git worktree. Each session gets its
|
|
128
|
+
own branch so it can't clobber the operator's main shell.
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
# Spawn a yolo on a bounded task
|
|
132
|
+
olam yolo my-task --branch feat/my-task --prompt-file /tmp/my-task.txt
|
|
133
|
+
|
|
134
|
+
# List active yolo windows
|
|
135
|
+
olam yolo --list
|
|
136
|
+
|
|
137
|
+
# Clean up after merge
|
|
138
|
+
olam yolo --cleanup my-task
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Full reference: `olam yolo --help` and [`docs/runbooks/olam-yolo-cli.md`](../../docs/runbooks/olam-yolo-cli.md).
|
|
142
|
+
|
|
124
143
|
## Multi-account Claude Code (`--claude-home`)
|
|
125
144
|
|
|
126
145
|
Inspired by [t3code](https://github.com/pingdotgg/t3code) — one operator,
|
package/bin/olam.cjs
CHANGED
|
@@ -16,6 +16,28 @@ if (!result.ok) {
|
|
|
16
16
|
process.exit(64);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Fast-path `--version` / `-V` BEFORE loading the ESM bundle (issue #988).
|
|
20
|
+
// Fresh-user installs run `olam --version` as the install-script sanity
|
|
21
|
+
// check; any hang in the bundle's import graph (better-sqlite3 native probe,
|
|
22
|
+
// agentmemory MCP context-loader, Dockerode lazy-binding) would block this
|
|
23
|
+
// path indefinitely. Reading package.json directly from the CJS shim
|
|
24
|
+
// guarantees `--version` returns in <100ms regardless of bundle state.
|
|
25
|
+
const argv = process.argv.slice(2);
|
|
26
|
+
if (argv.length === 1 && (argv[0] === '--version' || argv[0] === '-V')) {
|
|
27
|
+
try {
|
|
28
|
+
const fs = require('node:fs');
|
|
29
|
+
const path = require('node:path');
|
|
30
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
31
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
32
|
+
process.stdout.write(`${pkg.version || '0.0.0-unknown'}\n`);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
// Fall through to the bundle if the fast-path read fails for any reason
|
|
36
|
+
// (corrupt package.json, missing file). The bundle's own readCliVersion
|
|
37
|
+
// has the same fallback chain and will print '0.0.0-unknown' if needed.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
const { join } = require('node:path');
|
|
20
42
|
const { pathToFileURL } = require('node:url');
|
|
21
43
|
const bundlePath = join(__dirname, '..', 'dist', 'index.js');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/flywheel/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/flywheel/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAczC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmBvD"}
|
|
@@ -32,6 +32,8 @@ import { registerFlywheelDiversityCheck } from './diversity-check.js';
|
|
|
32
32
|
import { registerFlywheelPing } from './ping.js';
|
|
33
33
|
import { registerFlywheelInstallShims } from './install-shims.js';
|
|
34
34
|
import { registerFlywheelMigrateOverlays } from './migrate-overlays.js';
|
|
35
|
+
import { registerFlywheelSessionStart } from './session-start.js';
|
|
36
|
+
import { registerFlywheelInstallSessionStartHook } from './install-sessionstart-hook.js';
|
|
35
37
|
export function registerFlywheel(program) {
|
|
36
38
|
const flywheel = program
|
|
37
39
|
.command('flywheel')
|
|
@@ -46,5 +48,7 @@ export function registerFlywheel(program) {
|
|
|
46
48
|
registerFlywheelPing(flywheel);
|
|
47
49
|
registerFlywheelInstallShims(flywheel);
|
|
48
50
|
registerFlywheelMigrateOverlays(flywheel);
|
|
51
|
+
registerFlywheelSessionStart(flywheel);
|
|
52
|
+
registerFlywheelInstallSessionStartHook(flywheel);
|
|
49
53
|
}
|
|
50
54
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/flywheel/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,qCAAqC,EAAE,MAAM,8BAA8B,CAAC;AACrF,OAAO,EAAE,oCAAoC,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/flywheel/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,qCAAqC,EAAE,MAAM,8BAA8B,CAAC;AACrF,OAAO,EAAE,oCAAoC,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,uCAAuC,EAAE,MAAM,gCAAgC,CAAC;AAEzF,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,MAAM,QAAQ,GAAG,OAAO;SACrB,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CACV,qNAAqN,CACtN,CAAC;IAEJ,8BAA8B,CAAC,QAAQ,CAAC,CAAC;IACzC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAClC,0BAA0B,CAAC,QAAQ,CAAC,CAAC;IACrC,0BAA0B,CAAC,QAAQ,CAAC,CAAC;IACrC,qCAAqC,CAAC,QAAQ,CAAC,CAAC;IAChD,oCAAoC,CAAC,QAAQ,CAAC,CAAC;IAC/C,8BAA8B,CAAC,QAAQ,CAAC,CAAC;IACzC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC/B,4BAA4B,CAAC,QAAQ,CAAC,CAAC;IACvC,+BAA+B,CAAC,QAAQ,CAAC,CAAC;IAC1C,4BAA4B,CAAC,QAAQ,CAAC,CAAC;IACvC,uCAAuC,CAAC,QAAQ,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `olam flywheel install-sessionstart-hook [--scope project|user] [--uninstall]`
|
|
3
|
+
*
|
|
4
|
+
* Wires `olam flywheel session-start` into `<scope>/.claude/settings.json`
|
|
5
|
+
* hooks.SessionStart so Claude Code injects operator-state context (active
|
|
6
|
+
* workspace + active world + most-recent wake-brief) before the first user
|
|
7
|
+
* prompt. The 4th defense layer in Olam's hook chain (FF / PreToolUse /
|
|
8
|
+
* PostToolUse / Stop).
|
|
9
|
+
*
|
|
10
|
+
* Coexists with the existing skills-sync SessionStart hook
|
|
11
|
+
* (sentinel `olam-skills-sync-hook-v1`). Both entries sit side-by-side in
|
|
12
|
+
* the SessionStart array; Claude Code runs each matcher in order and merges
|
|
13
|
+
* their `additionalContext` outputs into the first turn's system prompt.
|
|
14
|
+
*
|
|
15
|
+
* Idempotent: re-running over an existing install is a no-op (sentinel match
|
|
16
|
+
* via `mergeHomeSettingsJson`).
|
|
17
|
+
*
|
|
18
|
+
* Pairs with: `docs/runbooks/install-sessionstart-hook.md`,
|
|
19
|
+
* `docs/decisions/051-sessionstart-hook-4th-defense.md`.
|
|
20
|
+
*/
|
|
21
|
+
import type { Command } from 'commander';
|
|
22
|
+
export declare const SESSIONSTART_HOOK_STAGE = "SessionStart";
|
|
23
|
+
export declare const SESSIONSTART_HOOK_TIMEOUT_MS = 5000;
|
|
24
|
+
interface SessionStartHookMatcher {
|
|
25
|
+
readonly matcher: string;
|
|
26
|
+
readonly hooks: ReadonlyArray<{
|
|
27
|
+
readonly type: 'command';
|
|
28
|
+
readonly command: string;
|
|
29
|
+
readonly timeout: number;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build the matcher-entry object that lands under hooks.SessionStart in
|
|
34
|
+
* .claude/settings.json. Embeds the sentinel as a leading no-op shell
|
|
35
|
+
* assignment so `command.includes(sentinel)` works for idempotent install +
|
|
36
|
+
* surgical uninstall, then guards against uninstalled-olam to keep the hook
|
|
37
|
+
* silent when the CLI is missing (audited by `audit:hook-guards`).
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildSessionStartHookEntry(): SessionStartHookMatcher;
|
|
40
|
+
export interface InstallResult {
|
|
41
|
+
readonly status: 'installed' | 'already-present' | 'no-op';
|
|
42
|
+
readonly filePath: string;
|
|
43
|
+
readonly backupPath: string | null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Install (idempotent) — programmatic API for both the CLI action and tests.
|
|
47
|
+
* Creates parent dir if needed; backs up an existing settings.json before
|
|
48
|
+
* writing (auto-deletes the backup when the install is a no-op).
|
|
49
|
+
*/
|
|
50
|
+
export declare function installSessionStartHook(filePath: string): InstallResult;
|
|
51
|
+
export interface UninstallResult {
|
|
52
|
+
readonly status: 'removed' | 'not-found' | 'no-settings';
|
|
53
|
+
readonly filePath: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Uninstall (surgical) — walks hooks.SessionStart, drops inner entries whose
|
|
57
|
+
* command contains the sentinel, drops the outer matcher when its hooks
|
|
58
|
+
* array becomes empty. Other SessionStart entries (e.g. the skills-sync
|
|
59
|
+
* hook) are preserved verbatim.
|
|
60
|
+
*/
|
|
61
|
+
export declare function uninstallSessionStartHook(filePath: string): UninstallResult;
|
|
62
|
+
export declare function registerFlywheelInstallSessionStartHook(parent: Command): void;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=install-sessionstart-hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-sessionstart-hook.d.ts","sourceRoot":"","sources":["../../../src/commands/flywheel/install-sessionstart-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKzC,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AACtD,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAIjD,UAAU,uBAAuB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;QAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;QACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;CACJ;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,IAAI,uBAAuB,CAWpE;AAsBD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,GAAG,OAAO,CAAC;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAsBvE;AAOD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAiD3E;AAED,wBAAgB,uCAAuC,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAsD7E"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `olam flywheel install-sessionstart-hook [--scope project|user] [--uninstall]`
|
|
3
|
+
*
|
|
4
|
+
* Wires `olam flywheel session-start` into `<scope>/.claude/settings.json`
|
|
5
|
+
* hooks.SessionStart so Claude Code injects operator-state context (active
|
|
6
|
+
* workspace + active world + most-recent wake-brief) before the first user
|
|
7
|
+
* prompt. The 4th defense layer in Olam's hook chain (FF / PreToolUse /
|
|
8
|
+
* PostToolUse / Stop).
|
|
9
|
+
*
|
|
10
|
+
* Coexists with the existing skills-sync SessionStart hook
|
|
11
|
+
* (sentinel `olam-skills-sync-hook-v1`). Both entries sit side-by-side in
|
|
12
|
+
* the SessionStart array; Claude Code runs each matcher in order and merges
|
|
13
|
+
* their `additionalContext` outputs into the first turn's system prompt.
|
|
14
|
+
*
|
|
15
|
+
* Idempotent: re-running over an existing install is a no-op (sentinel match
|
|
16
|
+
* via `mergeHomeSettingsJson`).
|
|
17
|
+
*
|
|
18
|
+
* Pairs with: `docs/runbooks/install-sessionstart-hook.md`,
|
|
19
|
+
* `docs/decisions/051-sessionstart-hook-4th-defense.md`.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, copyFileSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
import { dirname, join } from 'node:path';
|
|
24
|
+
import { mergeHomeSettingsJson } from '@olam/core/src/world/merge-settings.js';
|
|
25
|
+
import { printError, printInfo, printSuccess, printWarning } from '../../output.js';
|
|
26
|
+
import { SESSIONSTART_HOOK_SENTINEL } from './session-start.js';
|
|
27
|
+
export const SESSIONSTART_HOOK_STAGE = 'SessionStart';
|
|
28
|
+
export const SESSIONSTART_HOOK_TIMEOUT_MS = 5000;
|
|
29
|
+
const NOOP_GUARD = 'command -v olam >/dev/null 2>&1 || exit 0;';
|
|
30
|
+
/**
|
|
31
|
+
* Build the matcher-entry object that lands under hooks.SessionStart in
|
|
32
|
+
* .claude/settings.json. Embeds the sentinel as a leading no-op shell
|
|
33
|
+
* assignment so `command.includes(sentinel)` works for idempotent install +
|
|
34
|
+
* surgical uninstall, then guards against uninstalled-olam to keep the hook
|
|
35
|
+
* silent when the CLI is missing (audited by `audit:hook-guards`).
|
|
36
|
+
*/
|
|
37
|
+
export function buildSessionStartHookEntry() {
|
|
38
|
+
return {
|
|
39
|
+
matcher: '',
|
|
40
|
+
hooks: [
|
|
41
|
+
{
|
|
42
|
+
type: 'command',
|
|
43
|
+
command: `OLAM_SESSIONSTART_SENTINEL=${SESSIONSTART_HOOK_SENTINEL}; ${NOOP_GUARD} olam flywheel session-start`,
|
|
44
|
+
timeout: SESSIONSTART_HOOK_TIMEOUT_MS,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function settingsPathFor(scope, cwd) {
|
|
50
|
+
if (scope === 'user')
|
|
51
|
+
return join(homedir(), '.claude', 'settings.json');
|
|
52
|
+
return join(cwd ?? process.cwd(), '.claude', 'settings.json');
|
|
53
|
+
}
|
|
54
|
+
function backup(filePath) {
|
|
55
|
+
if (!existsSync(filePath))
|
|
56
|
+
return null;
|
|
57
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
58
|
+
const backupPath = `${filePath}.olam-bak.${ts}`;
|
|
59
|
+
copyFileSync(filePath, backupPath);
|
|
60
|
+
return backupPath;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Install (idempotent) — programmatic API for both the CLI action and tests.
|
|
64
|
+
* Creates parent dir if needed; backs up an existing settings.json before
|
|
65
|
+
* writing (auto-deletes the backup when the install is a no-op).
|
|
66
|
+
*/
|
|
67
|
+
export function installSessionStartHook(filePath) {
|
|
68
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
69
|
+
const backupPath = backup(filePath);
|
|
70
|
+
const result = mergeHomeSettingsJson(filePath, {
|
|
71
|
+
ensureHook: {
|
|
72
|
+
stage: SESSIONSTART_HOOK_STAGE,
|
|
73
|
+
sentinel: SESSIONSTART_HOOK_SENTINEL,
|
|
74
|
+
entry: buildSessionStartHookEntry(),
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
if (result.status === 'already-present' && backupPath) {
|
|
78
|
+
try {
|
|
79
|
+
unlinkSync(backupPath);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// best-effort
|
|
83
|
+
}
|
|
84
|
+
return { status: 'already-present', filePath, backupPath: null };
|
|
85
|
+
}
|
|
86
|
+
return { status: result.status, filePath, backupPath };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Uninstall (surgical) — walks hooks.SessionStart, drops inner entries whose
|
|
90
|
+
* command contains the sentinel, drops the outer matcher when its hooks
|
|
91
|
+
* array becomes empty. Other SessionStart entries (e.g. the skills-sync
|
|
92
|
+
* hook) are preserved verbatim.
|
|
93
|
+
*/
|
|
94
|
+
export function uninstallSessionStartHook(filePath) {
|
|
95
|
+
if (!existsSync(filePath))
|
|
96
|
+
return { status: 'no-settings', filePath };
|
|
97
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
98
|
+
const settings = raw.trim() ? JSON.parse(raw) : {};
|
|
99
|
+
const matchers = settings.hooks?.SessionStart;
|
|
100
|
+
if (!Array.isArray(matchers) || matchers.length === 0) {
|
|
101
|
+
return { status: 'not-found', filePath };
|
|
102
|
+
}
|
|
103
|
+
let changed = false;
|
|
104
|
+
const kept = [];
|
|
105
|
+
for (const matcher of matchers) {
|
|
106
|
+
const inner = matcher.hooks ?? [];
|
|
107
|
+
const keptInner = inner.filter((h) => {
|
|
108
|
+
if (typeof h.command === 'string' && h.command.includes(SESSIONSTART_HOOK_SENTINEL)) {
|
|
109
|
+
changed = true;
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
});
|
|
114
|
+
if (keptInner.length === 0 && inner.length > 0) {
|
|
115
|
+
changed = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (keptInner.length === inner.length) {
|
|
119
|
+
kept.push(matcher);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
kept.push({ ...matcher, hooks: keptInner });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!changed)
|
|
126
|
+
return { status: 'not-found', filePath };
|
|
127
|
+
const next = {
|
|
128
|
+
...settings,
|
|
129
|
+
hooks: { ...settings.hooks, SessionStart: kept },
|
|
130
|
+
};
|
|
131
|
+
if (kept.length === 0) {
|
|
132
|
+
const otherStages = Object.keys(next.hooks ?? {}).filter((k) => k !== 'SessionStart');
|
|
133
|
+
if (otherStages.length === 0) {
|
|
134
|
+
delete next.hooks;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
delete next.hooks.SessionStart;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
writeFileSync(filePath, JSON.stringify(next, null, 2) + '\n');
|
|
141
|
+
return { status: 'removed', filePath };
|
|
142
|
+
}
|
|
143
|
+
export function registerFlywheelInstallSessionStartHook(parent) {
|
|
144
|
+
parent
|
|
145
|
+
.command('install-sessionstart-hook')
|
|
146
|
+
.description('Install (or --uninstall) the SessionStart context hook into .claude/settings.json. Idempotent. 4th defense layer — preloads operator state before the first prompt.')
|
|
147
|
+
.option('--scope <scope>', 'project (default; <cwd>/.claude/settings.json) or user (~/.claude/settings.json)', 'project')
|
|
148
|
+
.option('--uninstall', 'Remove the hook instead of installing it')
|
|
149
|
+
.action((opts) => {
|
|
150
|
+
const scope = opts.scope === 'user' ? 'user' : 'project';
|
|
151
|
+
const filePath = settingsPathFor(scope);
|
|
152
|
+
if (opts.uninstall) {
|
|
153
|
+
try {
|
|
154
|
+
const r = uninstallSessionStartHook(filePath);
|
|
155
|
+
switch (r.status) {
|
|
156
|
+
case 'removed':
|
|
157
|
+
printSuccess(`SessionStart context hook removed from ${filePath}`);
|
|
158
|
+
return;
|
|
159
|
+
case 'not-found':
|
|
160
|
+
printInfo('sessionstart hook', `not found in ${filePath} — already uninstalled`);
|
|
161
|
+
return;
|
|
162
|
+
case 'no-settings':
|
|
163
|
+
printInfo('sessionstart hook', `no settings.json at ${filePath} — nothing to remove`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
printError(`uninstall failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
169
|
+
process.exitCode = 1;
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const r = installSessionStartHook(filePath);
|
|
175
|
+
switch (r.status) {
|
|
176
|
+
case 'installed':
|
|
177
|
+
printSuccess(`SessionStart context hook installed (${scope} scope)`);
|
|
178
|
+
printInfo('settings', r.filePath);
|
|
179
|
+
if (r.backupPath)
|
|
180
|
+
printInfo('backup', r.backupPath);
|
|
181
|
+
printInfo('next', 'open a new Claude Code session — Olam state will preload via additionalContext');
|
|
182
|
+
return;
|
|
183
|
+
case 'already-present':
|
|
184
|
+
printInfo('sessionstart hook', `already installed at ${r.filePath}`);
|
|
185
|
+
return;
|
|
186
|
+
case 'no-op':
|
|
187
|
+
printWarning(`no change made to ${r.filePath}`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
printError(`install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
193
|
+
process.exitCode = 1;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=install-sessionstart-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-sessionstart-hook.js","sourceRoot":"","sources":["../../../src/commands/flywheel/install-sessionstart-hook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `olam flywheel session-start` — emit operator-state context for the
|
|
3
|
+
* SessionStart hook (4th defense layer).
|
|
4
|
+
*
|
|
5
|
+
* Wire shape mirrors the standalone `scripts/hooks/session-start.mjs` so
|
|
6
|
+
* both surfaces are valid invocation paths:
|
|
7
|
+
* - settings.json `hooks.SessionStart` calls `olam flywheel session-start`.
|
|
8
|
+
* - Dev / Hermes / smoke-tests can run `node scripts/hooks/session-start.mjs`
|
|
9
|
+
* directly without touching the CLI.
|
|
10
|
+
*
|
|
11
|
+
* Contract (matches MJS verbatim):
|
|
12
|
+
* - stdout: `{}` when no state; else
|
|
13
|
+
* `{ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: "<text>" } }`
|
|
14
|
+
* - stderr: human-readable warnings only.
|
|
15
|
+
* - exit: ALWAYS 0 (fail-soft — never block session start).
|
|
16
|
+
*
|
|
17
|
+
* Budget: <50 ms. Reads at most 3 small files under ~/.olam/state/. No network.
|
|
18
|
+
*
|
|
19
|
+
* ADR: docs/decisions/051-sessionstart-hook-4th-defense.md
|
|
20
|
+
*/
|
|
21
|
+
import type { Command } from 'commander';
|
|
22
|
+
export declare const SESSIONSTART_HOOK_SENTINEL = "olam-sessionstart-context-hook-v1";
|
|
23
|
+
export declare function buildContextBlock(olamHome: string): string | null;
|
|
24
|
+
export declare function emitSessionStartContext(olamHome: string): void;
|
|
25
|
+
export declare function registerFlywheelSessionStart(parent: Command): void;
|
|
26
|
+
//# sourceMappingURL=session-start.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-start.d.ts","sourceRoot":"","sources":["../../../src/commands/flywheel/session-start.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,eAAO,MAAM,0BAA0B,sCAAsC,CAAC;AAkD9E,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsCjE;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAqB9D;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAYlE"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `olam flywheel session-start` — emit operator-state context for the
|
|
3
|
+
* SessionStart hook (4th defense layer).
|
|
4
|
+
*
|
|
5
|
+
* Wire shape mirrors the standalone `scripts/hooks/session-start.mjs` so
|
|
6
|
+
* both surfaces are valid invocation paths:
|
|
7
|
+
* - settings.json `hooks.SessionStart` calls `olam flywheel session-start`.
|
|
8
|
+
* - Dev / Hermes / smoke-tests can run `node scripts/hooks/session-start.mjs`
|
|
9
|
+
* directly without touching the CLI.
|
|
10
|
+
*
|
|
11
|
+
* Contract (matches MJS verbatim):
|
|
12
|
+
* - stdout: `{}` when no state; else
|
|
13
|
+
* `{ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: "<text>" } }`
|
|
14
|
+
* - stderr: human-readable warnings only.
|
|
15
|
+
* - exit: ALWAYS 0 (fail-soft — never block session start).
|
|
16
|
+
*
|
|
17
|
+
* Budget: <50 ms. Reads at most 3 small files under ~/.olam/state/. No network.
|
|
18
|
+
*
|
|
19
|
+
* ADR: docs/decisions/051-sessionstart-hook-4th-defense.md
|
|
20
|
+
*/
|
|
21
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
export const SESSIONSTART_HOOK_SENTINEL = 'olam-sessionstart-context-hook-v1';
|
|
25
|
+
const MAX_WAKE_BRIEF_BYTES = 8 * 1024;
|
|
26
|
+
const MAX_ACTIVE_WORLD_BYTES = 4 * 1024;
|
|
27
|
+
const MAX_CONFIG_BYTES = 8 * 1024;
|
|
28
|
+
function tryReadCapped(path, maxBytes) {
|
|
29
|
+
try {
|
|
30
|
+
const stat = statSync(path);
|
|
31
|
+
if (!stat.isFile() || stat.size === 0)
|
|
32
|
+
return null;
|
|
33
|
+
if (stat.size > maxBytes) {
|
|
34
|
+
process.stderr.write(`[${SESSIONSTART_HOOK_SENTINEL}] skipping ${path}: ${stat.size} bytes > ${maxBytes} cap\n`);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return readFileSync(path, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const code = err?.code;
|
|
41
|
+
if (code === 'ENOENT')
|
|
42
|
+
return null;
|
|
43
|
+
process.stderr.write(`[${SESSIONSTART_HOOK_SENTINEL}] read failed for ${path}: ${err?.message ?? err}\n`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function tryParseJson(raw, label) {
|
|
48
|
+
if (raw === null)
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(raw);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
process.stderr.write(`[${SESSIONSTART_HOOK_SENTINEL}] ${label} JSON parse failed: ${err?.message ?? err}\n`);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function buildContextBlock(olamHome) {
|
|
59
|
+
const config = tryParseJson(tryReadCapped(join(olamHome, 'config.json'), MAX_CONFIG_BYTES), 'config.json');
|
|
60
|
+
const activeWorld = tryParseJson(tryReadCapped(join(olamHome, 'state', 'active-world.json'), MAX_ACTIVE_WORLD_BYTES), 'active-world.json');
|
|
61
|
+
const wakeBrief = tryReadCapped(join(olamHome, 'state', 'wake-brief.md'), MAX_WAKE_BRIEF_BYTES);
|
|
62
|
+
const sections = [];
|
|
63
|
+
if (config && typeof config.activeWorkspace === 'string') {
|
|
64
|
+
sections.push(`Active workspace: ${config.activeWorkspace}`);
|
|
65
|
+
}
|
|
66
|
+
if (activeWorld && typeof activeWorld === 'object') {
|
|
67
|
+
const parts = [];
|
|
68
|
+
if (typeof activeWorld.worldId === 'string')
|
|
69
|
+
parts.push(`worldId=${activeWorld.worldId}`);
|
|
70
|
+
if (typeof activeWorld.branch === 'string')
|
|
71
|
+
parts.push(`branch=${activeWorld.branch}`);
|
|
72
|
+
if (typeof activeWorld.repo === 'string')
|
|
73
|
+
parts.push(`repo=${activeWorld.repo}`);
|
|
74
|
+
if (typeof activeWorld.tier === 'string')
|
|
75
|
+
parts.push(`tier=${activeWorld.tier}`);
|
|
76
|
+
if (typeof activeWorld.enteredAt === 'string')
|
|
77
|
+
parts.push(`enteredAt=${activeWorld.enteredAt}`);
|
|
78
|
+
if (parts.length > 0)
|
|
79
|
+
sections.push(`Active world: ${parts.join(', ')}`);
|
|
80
|
+
}
|
|
81
|
+
if (wakeBrief && wakeBrief.trim().length > 0) {
|
|
82
|
+
sections.push('Most recent wake brief:\n\n' + wakeBrief.trimEnd());
|
|
83
|
+
}
|
|
84
|
+
if (sections.length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
const body = sections.join('\n\n');
|
|
87
|
+
return `Olam SessionStart context (${Buffer.byteLength(body, 'utf-8')} bytes):\n\n${body}`;
|
|
88
|
+
}
|
|
89
|
+
export function emitSessionStartContext(olamHome) {
|
|
90
|
+
try {
|
|
91
|
+
const context = buildContextBlock(olamHome);
|
|
92
|
+
if (context === null) {
|
|
93
|
+
process.stdout.write('{}\n');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
process.stdout.write(JSON.stringify({
|
|
97
|
+
hookSpecificOutput: {
|
|
98
|
+
hookEventName: 'SessionStart',
|
|
99
|
+
additionalContext: context,
|
|
100
|
+
},
|
|
101
|
+
}) + '\n');
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
process.stderr.write(`[${SESSIONSTART_HOOK_SENTINEL}] unexpected error: ${err?.message ?? err}\n`);
|
|
105
|
+
process.stdout.write('{}\n');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function registerFlywheelSessionStart(parent) {
|
|
109
|
+
parent
|
|
110
|
+
.command('session-start')
|
|
111
|
+
.description('Emit operator-state context for the SessionStart hook (4th defense layer). Reads ~/.olam/state/* and writes the additionalContext JSON to stdout. Fail-soft: always exits 0.')
|
|
112
|
+
.action(() => {
|
|
113
|
+
const olamHome = process.env.OLAM_HOME ?? join(homedir(), '.olam');
|
|
114
|
+
emitSessionStartContext(olamHome);
|
|
115
|
+
// Always exit 0 — the hook must NEVER block session start.
|
|
116
|
+
process.exit(0);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=session-start.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../../src/commands/flywheel/session-start.ts"],"names":[],"mappings":"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"}
|
|
@@ -81,9 +81,6 @@ interface ContainerInfo {
|
|
|
81
81
|
readonly state: string;
|
|
82
82
|
readonly status: string;
|
|
83
83
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Find the host-cp container by name. Returns null if absent.
|
|
86
|
-
*/
|
|
87
84
|
export declare function findHostCpContainer(): Promise<ContainerInfo | null>;
|
|
88
85
|
/**
|
|
89
86
|
* Phase host-cp-reliability: Probe for a running host CP, supporting
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-cp.d.ts","sourceRoot":"","sources":["../../src/commands/host-cp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAezC,mEAAmE;AACnE,eAAO,MAAM,YAAY,QAAQ,CAAC;AAElC;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAcxC;AAeD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAK9C;AAID,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,aAAa,GAAG,IAAI,CA4BxD;AAID;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAMnC;AAED,wBAAgB,SAAS,IAAI,MAAM,GAAG,IAAI,CAIzC;AAED,wBAAgB,WAAW,IAAI,OAAO,CAKrC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAI1C;AAED,wBAAgB,OAAO,IAAI,MAAM,GAAG,IAAI,CAMvC;AAED,wBAAgB,SAAS,IAAI,OAAO,CAKnC;AAID,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"host-cp.d.ts","sourceRoot":"","sources":["../../src/commands/host-cp.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/host-cp.js
CHANGED
|
@@ -180,11 +180,36 @@ export function removePid() {
|
|
|
180
180
|
return true;
|
|
181
181
|
}
|
|
182
182
|
/**
|
|
183
|
-
* Find the host-cp container by name. Returns null if absent
|
|
183
|
+
* Find the host-cp container by name. Returns null if absent OR if the
|
|
184
|
+
* Docker daemon is unresponsive within `DOCKER_PROBE_TIMEOUT_MS`.
|
|
185
|
+
*
|
|
186
|
+
* The timeout is critical for issue #988: `olam status` calls this via
|
|
187
|
+
* `probeHostCp()`; without a timeout, a wedged Docker daemon (Colima with
|
|
188
|
+
* stale state, dockerd-rootless socket present but unresponsive, etc.)
|
|
189
|
+
* makes the command hang indefinitely. Read-only commands must never hang
|
|
190
|
+
* waiting on backing services.
|
|
184
191
|
*/
|
|
192
|
+
const DOCKER_PROBE_TIMEOUT_MS = 2_000;
|
|
185
193
|
export async function findHostCpContainer() {
|
|
186
194
|
const docker = new Dockerode(resolveDockerHostOptions());
|
|
187
|
-
|
|
195
|
+
let timer = null;
|
|
196
|
+
const listing = docker.listContainers({ all: true }).then((cs) => cs);
|
|
197
|
+
const timeout = new Promise((resolve) => {
|
|
198
|
+
timer = setTimeout(() => resolve(null), DOCKER_PROBE_TIMEOUT_MS);
|
|
199
|
+
});
|
|
200
|
+
let containers;
|
|
201
|
+
try {
|
|
202
|
+
containers = (await Promise.race([listing, timeout]));
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
containers = null;
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
if (timer !== null)
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
}
|
|
211
|
+
if (containers === null)
|
|
212
|
+
return null;
|
|
188
213
|
for (const c of containers) {
|
|
189
214
|
const names = (c.Names ?? []).map((n) => n.replace(/^\//, ''));
|
|
190
215
|
if (names.includes('olam-host-cp')) {
|