switchroom 0.10.0 → 0.11.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 +5 -4
- package/dist/cli/drive-write-pretool.mjs +5418 -0
- package/dist/cli/switchroom.js +201 -24
- package/package.json +1 -1
- package/telegram-plugin/admin-commands/dispatch.test.ts +1 -1
- package/telegram-plugin/admin-commands/index.ts +2 -0
- package/telegram-plugin/auth-snapshot-format.ts +612 -0
- package/telegram-plugin/auto-fallback-fleet.ts +215 -0
- package/telegram-plugin/auto-fallback.ts +28 -301
- package/telegram-plugin/dist/gateway/gateway.js +4407 -2252
- package/telegram-plugin/fleet-fallback-gate.ts +105 -0
- package/telegram-plugin/gateway/approval-callback.test.ts +104 -0
- package/telegram-plugin/gateway/approval-callback.ts +31 -3
- package/telegram-plugin/gateway/auth-command.ts +121 -10
- package/telegram-plugin/gateway/auth-status-adapter.ts +101 -0
- package/telegram-plugin/gateway/boot-card.ts +1 -1
- package/telegram-plugin/gateway/boot-probes.ts +6 -9
- package/telegram-plugin/gateway/diff-preview-card.test.ts +192 -0
- package/telegram-plugin/gateway/diff-preview-card.ts +170 -0
- package/telegram-plugin/gateway/drive-write-approval.test.ts +312 -0
- package/telegram-plugin/gateway/drive-write-approval.ts +243 -0
- package/telegram-plugin/gateway/folder-picker-handler.test.ts +314 -0
- package/telegram-plugin/gateway/folder-picker-handler.ts +348 -0
- package/telegram-plugin/gateway/gateway.ts +876 -173
- package/telegram-plugin/gateway/hostd-dispatch.ts +127 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +83 -2
- package/telegram-plugin/gateway/ipc-server.ts +69 -0
- package/telegram-plugin/hooks/sandbox-hint-posttool.mjs +103 -12
- package/telegram-plugin/model-unavailable.ts +28 -12
- package/telegram-plugin/silence-poke.ts +153 -1
- package/telegram-plugin/tests/auth-command-format2.test.ts +156 -0
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +429 -0
- package/telegram-plugin/tests/auth-status-adapter.test.ts +129 -0
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +211 -0
- package/telegram-plugin/tests/auto-fallback.test.ts +60 -358
- package/telegram-plugin/tests/boot-probes.test.ts +16 -18
- package/telegram-plugin/tests/fleet-fallback-gate.test.ts +197 -0
- package/telegram-plugin/tests/model-unavailable.test.ts +30 -5
- package/telegram-plugin/tests/sandbox-hint-posttool.test.ts +212 -2
- package/telegram-plugin/tests/silence-poke.test.ts +237 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +112 -0
- package/telegram-plugin/turn-flush-safety.ts +55 -1
- package/telegram-plugin/uat/SETUP.md +16 -12
- package/telegram-plugin/auto-fallback-dispatcher.ts +0 -68
- package/telegram-plugin/tests/auto-fallback-dispatcher.e2e.test.ts +0 -183
- package/telegram-plugin/tests/hostd-dispatch.test.ts +0 -129
package/dist/cli/switchroom.js
CHANGED
|
@@ -22860,6 +22860,7 @@ function generateCompose(opts) {
|
|
|
22860
22860
|
}
|
|
22861
22861
|
for (const c of authConsumers) {
|
|
22862
22862
|
lines.push(` auth-broker-${c.name}-sock:`);
|
|
22863
|
+
lines.push(` name: auth-broker-${c.name}-sock`);
|
|
22863
22864
|
}
|
|
22864
22865
|
lines.push("");
|
|
22865
22866
|
return lines.join(`
|
|
@@ -25660,17 +25661,22 @@ function checkHindsightConsumer(config, opts) {
|
|
|
25660
25661
|
` + "then run `switchroom apply` to bind the per-consumer socket."
|
|
25661
25662
|
};
|
|
25662
25663
|
}
|
|
25663
|
-
const
|
|
25664
|
-
const
|
|
25665
|
-
|
|
25666
|
-
|
|
25667
|
-
|
|
25668
|
-
|
|
25664
|
+
const probe2 = opts?.socketProbe ?? probeAuthBrokerSocket;
|
|
25665
|
+
const state = probe2(entry.name);
|
|
25666
|
+
if (state === "unreachable") {
|
|
25667
|
+
return {
|
|
25668
|
+
name: "hindsight consumer",
|
|
25669
|
+
status: "warn",
|
|
25670
|
+
detail: `auth.consumers[hindsight] -> ${entry.account} (uid ${entry.uid ?? 0}); ` + `couldn't query auth-broker container (not running / docker unavailable)`,
|
|
25671
|
+
fix: "Check `auth-broker: service health` row above; if the broker is " + "down, `switchroom apply` will bring it back and bind the socket."
|
|
25672
|
+
};
|
|
25673
|
+
}
|
|
25674
|
+
if (state === "missing") {
|
|
25669
25675
|
return {
|
|
25670
25676
|
name: "hindsight consumer",
|
|
25671
25677
|
status: "warn",
|
|
25672
|
-
detail: `auth.consumers[hindsight] -> ${entry.account} (uid ${entry.uid ?? 0}); ` + `socket not
|
|
25673
|
-
fix: "Run `switchroom apply` to
|
|
25678
|
+
detail: `auth.consumers[hindsight] -> ${entry.account} (uid ${entry.uid ?? 0}); ` + `auth-broker is running but socket not bound at /run/switchroom/auth-broker/${entry.name}/sock`,
|
|
25679
|
+
fix: "Run `switchroom apply` to refresh compose and rebind per-consumer sockets."
|
|
25674
25680
|
};
|
|
25675
25681
|
}
|
|
25676
25682
|
return {
|
|
@@ -25679,6 +25685,17 @@ function checkHindsightConsumer(config, opts) {
|
|
|
25679
25685
|
detail: `auth.consumers[hindsight] -> ${entry.account} (uid ${entry.uid ?? 0})`
|
|
25680
25686
|
};
|
|
25681
25687
|
}
|
|
25688
|
+
function probeAuthBrokerSocket(consumerName) {
|
|
25689
|
+
const containerPath = `/run/switchroom/auth-broker/${consumerName}/sock`;
|
|
25690
|
+
const r = spawnSync3("docker", ["exec", "switchroom-auth-broker", "test", "-S", containerPath], { stdio: "pipe", timeout: 3000 });
|
|
25691
|
+
if (r.error || r.status === null)
|
|
25692
|
+
return "unreachable";
|
|
25693
|
+
if (r.status === 0)
|
|
25694
|
+
return "present";
|
|
25695
|
+
if (r.status >= 125)
|
|
25696
|
+
return "unreachable";
|
|
25697
|
+
return "missing";
|
|
25698
|
+
}
|
|
25682
25699
|
async function checkHindsight(config) {
|
|
25683
25700
|
const memoryBackend = config.memory?.backend;
|
|
25684
25701
|
if (memoryBackend !== "hindsight") {
|
|
@@ -27911,14 +27928,14 @@ var init_oauth = __esm(() => {
|
|
|
27911
27928
|
|
|
27912
27929
|
// src/drive/grants.ts
|
|
27913
27930
|
function scopeFor(target, action) {
|
|
27914
|
-
const
|
|
27931
|
+
const actionPrefix = action === "read" ? "" : `${action}:`;
|
|
27915
27932
|
switch (target.kind) {
|
|
27916
27933
|
case "all":
|
|
27917
|
-
return `doc:gdrive:${
|
|
27934
|
+
return `doc:gdrive:${actionPrefix}**`;
|
|
27918
27935
|
case "folder":
|
|
27919
|
-
return `doc:gdrive:${
|
|
27936
|
+
return `doc:gdrive:${actionPrefix}folder/${target.folder_id}/**`;
|
|
27920
27937
|
case "doc":
|
|
27921
|
-
return `doc:gdrive:${
|
|
27938
|
+
return `doc:gdrive:${actionPrefix}${target.doc_id}`;
|
|
27922
27939
|
}
|
|
27923
27940
|
}
|
|
27924
27941
|
|
|
@@ -45296,8 +45313,8 @@ var {
|
|
|
45296
45313
|
} = import__.default;
|
|
45297
45314
|
|
|
45298
45315
|
// src/build-info.ts
|
|
45299
|
-
var VERSION = "0.
|
|
45300
|
-
var COMMIT_SHA = "
|
|
45316
|
+
var VERSION = "0.11.0";
|
|
45317
|
+
var COMMIT_SHA = "abff20c7";
|
|
45301
45318
|
|
|
45302
45319
|
// src/cli/deprecated.ts
|
|
45303
45320
|
init_source();
|
|
@@ -47207,6 +47224,7 @@ Don't wait for a slash command. Don't ask permission. Memory work is table stake
|
|
|
47207
47224
|
}
|
|
47208
47225
|
var DOCKER_TELEGRAM_PLUGIN_PATH = "/opt/switchroom/telegram-plugin";
|
|
47209
47226
|
var DOCKER_HOOKS_PATH = `${DOCKER_TELEGRAM_PLUGIN_PATH}/hooks`;
|
|
47227
|
+
var DOCKER_BUNDLED_HOOKS_PATH = "/opt/switchroom/hooks";
|
|
47210
47228
|
var DOCKER_BIN_PATH = "/opt/switchroom/bin";
|
|
47211
47229
|
var DOCKER_CONFIG_PATH = "/state/config/switchroom.yaml";
|
|
47212
47230
|
function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchroomConfig, userIdOverride, switchroomConfigPath) {
|
|
@@ -47735,6 +47753,16 @@ function buildSettingsHooksBlock(p) {
|
|
|
47735
47753
|
}
|
|
47736
47754
|
]
|
|
47737
47755
|
},
|
|
47756
|
+
{
|
|
47757
|
+
matcher: "^mcp__google-workspace__",
|
|
47758
|
+
hooks: [
|
|
47759
|
+
{
|
|
47760
|
+
type: "command",
|
|
47761
|
+
command: wrap("hook:drive-write-pretool", `node "${join8(DOCKER_BUNDLED_HOOKS_PATH, "drive-write-pretool.mjs")}"`),
|
|
47762
|
+
timeout: 5 * 60 + 30
|
|
47763
|
+
}
|
|
47764
|
+
]
|
|
47765
|
+
},
|
|
47738
47766
|
{
|
|
47739
47767
|
hooks: [
|
|
47740
47768
|
{
|
|
@@ -47757,7 +47785,7 @@ function buildSettingsHooksBlock(p) {
|
|
|
47757
47785
|
]
|
|
47758
47786
|
},
|
|
47759
47787
|
{
|
|
47760
|
-
matcher: ".*",
|
|
47788
|
+
matcher: "^(Edit|MultiEdit|Write|NotebookEdit|Bash|mcp__.*)$",
|
|
47761
47789
|
hooks: [
|
|
47762
47790
|
{
|
|
47763
47791
|
type: "command",
|
|
@@ -64973,6 +65001,8 @@ var HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000;
|
|
|
64973
65001
|
var HINDSIGHT_CONSUMER_NAME = "hindsight";
|
|
64974
65002
|
var HINDSIGHT_DEFAULT_UID = 11000;
|
|
64975
65003
|
var HINDSIGHT_IMAGE = "ghcr.io/switchroom/switchroom-hindsight:latest";
|
|
65004
|
+
var HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
65005
|
+
var HINDSIGHT_DEFAULT_MCP_STATELESS = true;
|
|
64976
65006
|
var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
|
|
64977
65007
|
function isPortFree(port) {
|
|
64978
65008
|
return new Promise((resolve28) => {
|
|
@@ -65040,7 +65070,11 @@ function startHindsight(ports) {
|
|
|
65040
65070
|
"-e",
|
|
65041
65071
|
`HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
|
|
65042
65072
|
"-e",
|
|
65043
|
-
"HINDSIGHT_API_LLM_PROVIDER=claude-code"
|
|
65073
|
+
"HINDSIGHT_API_LLM_PROVIDER=claude-code",
|
|
65074
|
+
"-e",
|
|
65075
|
+
`HINDSIGHT_API_LLM_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
|
|
65076
|
+
"-e",
|
|
65077
|
+
`HINDSIGHT_API_MCP_STATELESS=${HINDSIGHT_DEFAULT_MCP_STATELESS}`
|
|
65044
65078
|
];
|
|
65045
65079
|
const args = [
|
|
65046
65080
|
"run",
|
|
@@ -65058,7 +65092,7 @@ function startHindsight(ports) {
|
|
|
65058
65092
|
"-v",
|
|
65059
65093
|
`${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`,
|
|
65060
65094
|
"--tmpfs",
|
|
65061
|
-
|
|
65095
|
+
`/run/claude-creds:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`,
|
|
65062
65096
|
...envArgs,
|
|
65063
65097
|
HINDSIGHT_IMAGE
|
|
65064
65098
|
];
|
|
@@ -65093,11 +65127,13 @@ function generateHindsightComposeSnippet() {
|
|
|
65093
65127
|
" environment:",
|
|
65094
65128
|
` - HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
|
|
65095
65129
|
" - HINDSIGHT_API_LLM_PROVIDER=claude-code",
|
|
65130
|
+
` - HINDSIGHT_API_LLM_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
|
|
65131
|
+
` - HINDSIGHT_API_MCP_STATELESS=${HINDSIGHT_DEFAULT_MCP_STATELESS}`,
|
|
65096
65132
|
" volumes:",
|
|
65097
65133
|
" - switchroom-hindsight-data:/home/hindsight/.pg0",
|
|
65098
65134
|
` - ${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`,
|
|
65099
65135
|
" tmpfs:",
|
|
65100
|
-
|
|
65136
|
+
` - /run/claude-creds:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`,
|
|
65101
65137
|
" restart: unless-stopped",
|
|
65102
65138
|
"",
|
|
65103
65139
|
"volumes:",
|
|
@@ -72574,10 +72610,109 @@ function registerMigrateCommand(program3) {
|
|
|
72574
72610
|
// src/cli/hostd.ts
|
|
72575
72611
|
init_source();
|
|
72576
72612
|
init_helpers();
|
|
72577
|
-
import { existsSync as existsSync65, mkdirSync as mkdirSync35, readdirSync as readdirSync26, writeFileSync as writeFileSync31, statSync as statSync26, copyFileSync as copyFileSync11 } from "node:fs";
|
|
72613
|
+
import { existsSync as existsSync65, mkdirSync as mkdirSync35, readdirSync as readdirSync26, readFileSync as readFileSync57, writeFileSync as writeFileSync31, statSync as statSync26, copyFileSync as copyFileSync11 } from "node:fs";
|
|
72614
|
+
import { homedir as homedir28 } from "node:os";
|
|
72615
|
+
import { join as join54 } from "node:path";
|
|
72616
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
72617
|
+
|
|
72618
|
+
// src/host-control/audit-reader.ts
|
|
72578
72619
|
import { homedir as homedir27 } from "node:os";
|
|
72579
72620
|
import { join as join53 } from "node:path";
|
|
72580
|
-
|
|
72621
|
+
function defaultAuditLogPath2(home2 = homedir27()) {
|
|
72622
|
+
return join53(home2, ".switchroom", "host-control-audit.log");
|
|
72623
|
+
}
|
|
72624
|
+
function parseAuditLine2(line) {
|
|
72625
|
+
const trimmed = line.trim();
|
|
72626
|
+
if (trimmed.length === 0)
|
|
72627
|
+
return null;
|
|
72628
|
+
let obj;
|
|
72629
|
+
try {
|
|
72630
|
+
obj = JSON.parse(trimmed);
|
|
72631
|
+
} catch {
|
|
72632
|
+
return null;
|
|
72633
|
+
}
|
|
72634
|
+
if (typeof obj !== "object" || obj === null)
|
|
72635
|
+
return null;
|
|
72636
|
+
const o = obj;
|
|
72637
|
+
if (typeof o.ts !== "string" || typeof o.op !== "string")
|
|
72638
|
+
return null;
|
|
72639
|
+
if (typeof o.request_id !== "string" || typeof o.result !== "string")
|
|
72640
|
+
return null;
|
|
72641
|
+
if (typeof o.duration_ms !== "number")
|
|
72642
|
+
return null;
|
|
72643
|
+
const callerRaw = o.caller;
|
|
72644
|
+
let caller;
|
|
72645
|
+
if (callerRaw && callerRaw.kind === "agent" && typeof callerRaw.name === "string") {
|
|
72646
|
+
caller = { kind: "agent", name: callerRaw.name };
|
|
72647
|
+
} else if (callerRaw && callerRaw.kind === "operator") {
|
|
72648
|
+
caller = { kind: "operator" };
|
|
72649
|
+
} else {
|
|
72650
|
+
return null;
|
|
72651
|
+
}
|
|
72652
|
+
const exit_code = o.exit_code === null || typeof o.exit_code === "number" ? o.exit_code : null;
|
|
72653
|
+
const entry = {
|
|
72654
|
+
ts: o.ts,
|
|
72655
|
+
op: o.op,
|
|
72656
|
+
caller,
|
|
72657
|
+
request_id: o.request_id,
|
|
72658
|
+
result: o.result,
|
|
72659
|
+
exit_code,
|
|
72660
|
+
duration_ms: o.duration_ms
|
|
72661
|
+
};
|
|
72662
|
+
if (typeof o.error === "string")
|
|
72663
|
+
entry.error = o.error;
|
|
72664
|
+
return entry;
|
|
72665
|
+
}
|
|
72666
|
+
function filterEntries(entries, filters) {
|
|
72667
|
+
return entries.filter((e) => {
|
|
72668
|
+
if (filters.agent != null) {
|
|
72669
|
+
if (e.caller.kind !== "agent")
|
|
72670
|
+
return false;
|
|
72671
|
+
if (e.caller.name !== filters.agent)
|
|
72672
|
+
return false;
|
|
72673
|
+
}
|
|
72674
|
+
if (filters.op != null && e.op !== filters.op)
|
|
72675
|
+
return false;
|
|
72676
|
+
if (filters.errorOnly) {
|
|
72677
|
+
if (e.result !== "error" && e.result !== "denied")
|
|
72678
|
+
return false;
|
|
72679
|
+
}
|
|
72680
|
+
return true;
|
|
72681
|
+
});
|
|
72682
|
+
}
|
|
72683
|
+
function readAndFilter(raw, filters, limit) {
|
|
72684
|
+
const lines = raw.split(`
|
|
72685
|
+
`);
|
|
72686
|
+
const parsed = [];
|
|
72687
|
+
for (const line of lines) {
|
|
72688
|
+
const e = parseAuditLine2(line);
|
|
72689
|
+
if (e != null)
|
|
72690
|
+
parsed.push(e);
|
|
72691
|
+
}
|
|
72692
|
+
const filtered = filterEntries(parsed, filters);
|
|
72693
|
+
return filtered.slice(-Math.max(1, limit));
|
|
72694
|
+
}
|
|
72695
|
+
function shortCaller(caller) {
|
|
72696
|
+
return caller.kind === "agent" ? caller.name : "operator";
|
|
72697
|
+
}
|
|
72698
|
+
function shortTs(ts) {
|
|
72699
|
+
return ts.replace("T", " ").replace(/\.\d+Z$/, "").slice(0, 19);
|
|
72700
|
+
}
|
|
72701
|
+
function formatForCli(entries) {
|
|
72702
|
+
const out = [];
|
|
72703
|
+
for (const e of entries) {
|
|
72704
|
+
const ts = shortTs(e.ts).padEnd(20);
|
|
72705
|
+
const caller = shortCaller(e.caller).padEnd(15);
|
|
72706
|
+
const op = e.op.padEnd(16);
|
|
72707
|
+
const result = e.result.padEnd(10);
|
|
72708
|
+
const exit = e.exit_code == null ? " -" : String(e.exit_code).padStart(3);
|
|
72709
|
+
const ms = `${e.duration_ms}ms`.padStart(8);
|
|
72710
|
+
out.push(`${ts} ${caller} ${op} ${result} ${exit} ${ms}`);
|
|
72711
|
+
}
|
|
72712
|
+
return out;
|
|
72713
|
+
}
|
|
72714
|
+
|
|
72715
|
+
// src/cli/hostd.ts
|
|
72581
72716
|
var DEFAULT_IMAGE_TAG = "latest";
|
|
72582
72717
|
var HOSTD_COMPOSE_PROJECT = "switchroom-hostd";
|
|
72583
72718
|
function renderHostdComposeFile(opts) {
|
|
@@ -72660,10 +72795,10 @@ networks:
|
|
|
72660
72795
|
`;
|
|
72661
72796
|
}
|
|
72662
72797
|
function hostdDir() {
|
|
72663
|
-
return
|
|
72798
|
+
return join54(homedir28(), ".switchroom", "hostd");
|
|
72664
72799
|
}
|
|
72665
72800
|
function hostdComposePath() {
|
|
72666
|
-
return
|
|
72801
|
+
return join54(hostdDir(), "docker-compose.yml");
|
|
72667
72802
|
}
|
|
72668
72803
|
function backupExistingCompose() {
|
|
72669
72804
|
const p = hostdComposePath();
|
|
@@ -72702,7 +72837,7 @@ async function doInstall(opts, program3) {
|
|
|
72702
72837
|
const composePath = hostdComposePath();
|
|
72703
72838
|
mkdirSync35(dir, { recursive: true });
|
|
72704
72839
|
const yaml = renderHostdComposeFile({
|
|
72705
|
-
hostHome:
|
|
72840
|
+
hostHome: homedir28(),
|
|
72706
72841
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG
|
|
72707
72842
|
});
|
|
72708
72843
|
if (opts.dryRun) {
|
|
@@ -72770,7 +72905,7 @@ function doStatus() {
|
|
|
72770
72905
|
for (const name of readdirSync26(dir)) {
|
|
72771
72906
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
72772
72907
|
continue;
|
|
72773
|
-
const sockPath =
|
|
72908
|
+
const sockPath = join54(dir, name, "sock");
|
|
72774
72909
|
if (existsSync65(sockPath)) {
|
|
72775
72910
|
const st = statSync26(sockPath);
|
|
72776
72911
|
if ((st.mode & 61440) === 49152) {
|
|
@@ -72812,6 +72947,48 @@ function registerHostdCommand(program3) {
|
|
|
72812
72947
|
}));
|
|
72813
72948
|
hostd.command("status").description("Show daemon state and bound sockets").action(() => doStatus());
|
|
72814
72949
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
72950
|
+
hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
|
|
72951
|
+
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
72952
|
+
if (!existsSync65(logPath)) {
|
|
72953
|
+
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
72954
|
+
The log is created when hostd handles its first privileged-verb request.`));
|
|
72955
|
+
return;
|
|
72956
|
+
}
|
|
72957
|
+
const raw = readFileSync57(logPath, "utf-8");
|
|
72958
|
+
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
72959
|
+
const filters = {
|
|
72960
|
+
agent: opts.agent,
|
|
72961
|
+
op: opts.op,
|
|
72962
|
+
errorOnly: !!opts.error
|
|
72963
|
+
};
|
|
72964
|
+
const entries = readAndFilter(raw, filters, limit);
|
|
72965
|
+
if (entries.length === 0) {
|
|
72966
|
+
const parts = [];
|
|
72967
|
+
if (opts.agent)
|
|
72968
|
+
parts.push(`agent=${opts.agent}`);
|
|
72969
|
+
if (opts.op)
|
|
72970
|
+
parts.push(`op=${opts.op}`);
|
|
72971
|
+
if (opts.error)
|
|
72972
|
+
parts.push("errors-only");
|
|
72973
|
+
const desc = parts.length > 0 ? ` matching ${parts.join(", ")}` : "";
|
|
72974
|
+
console.log(source_default.dim(`No hostd audit entries${desc}.`));
|
|
72975
|
+
return;
|
|
72976
|
+
}
|
|
72977
|
+
const header = "ts".padEnd(20) + " " + "caller".padEnd(15) + " " + "op".padEnd(16) + " " + "result".padEnd(10) + " " + "exit".padStart(3) + " " + "dur".padStart(8);
|
|
72978
|
+
console.log(source_default.dim(header));
|
|
72979
|
+
console.log(source_default.dim("\u2500".repeat(header.length)));
|
|
72980
|
+
for (const line of formatForCli(entries)) {
|
|
72981
|
+
if (line.includes(" error ") || line.includes(" denied ")) {
|
|
72982
|
+
console.log(source_default.red(line));
|
|
72983
|
+
} else if (line.includes(" started ")) {
|
|
72984
|
+
console.log(source_default.yellow(line));
|
|
72985
|
+
} else {
|
|
72986
|
+
console.log(line);
|
|
72987
|
+
}
|
|
72988
|
+
}
|
|
72989
|
+
console.log();
|
|
72990
|
+
console.log(source_default.dim(`${entries.length} entr${entries.length === 1 ? "y" : "ies"} shown` + (entries.length === limit ? ` (--tail ${limit})` : "") + ` \u00b7 log: ${logPath}`));
|
|
72991
|
+
});
|
|
72815
72992
|
}
|
|
72816
72993
|
|
|
72817
72994
|
// src/cli/index.ts
|
package/package.json
CHANGED
|
@@ -41,7 +41,7 @@ describe('parseCommandName', () => {
|
|
|
41
41
|
|
|
42
42
|
describe('ADMIN_COMMAND_NAMES', () => {
|
|
43
43
|
it('contains the fleet-management admin commands', () => {
|
|
44
|
-
const required = ['agents', 'logs', 'restart', 'update', 'reconcile', 'stop', 'agentstart', 'grant', 'dangerous', 'permissions', 'vault']
|
|
44
|
+
const required = ['agents', 'logs', 'restart', 'update', 'reconcile', 'stop', 'agentstart', 'grant', 'dangerous', 'permissions', 'vault', 'audit']
|
|
45
45
|
for (const cmd of required) {
|
|
46
46
|
expect(ADMIN_COMMAND_NAMES.has(cmd)).toBe(true)
|
|
47
47
|
}
|