@getfamiliar/shared 0.1.13 → 0.1.15
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/build/DockerTools.d.ts +119 -0
- package/build/DockerTools.js +175 -0
- package/build/WebServerDefaults.d.ts +9 -0
- package/build/WebServerDefaults.js +9 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared bridge network all Familiar containers join. Owned
|
|
3
|
+
* by the daemon (see `commands/Start.ts`); created once via
|
|
4
|
+
* {@link ensureNetwork} before any container is started.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SHARED_NETWORK_NAME = "familiar-net";
|
|
7
|
+
/**
|
|
8
|
+
* Internal (egress-less) bridge network the locked-down agent container
|
|
9
|
+
* joins. Created with `--internal` so the agent has no route to the
|
|
10
|
+
* internet; it reaches postgres (dual-homed onto this net) and the host
|
|
11
|
+
* bastion (via the `familiar-bastion-bridge` socat sidecar, also on this
|
|
12
|
+
* net) by container name. Owned by the daemon, created via
|
|
13
|
+
* {@link ensureNetwork} with `{ internal: true }`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const ISOLATED_NETWORK_NAME = "familiar-isolated";
|
|
16
|
+
/** Options shared by {@link dockerExec}. */
|
|
17
|
+
export interface DockerExecOptions {
|
|
18
|
+
/** If true, non-zero exit codes resolve instead of throwing. */
|
|
19
|
+
readonly allowFailure?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Output of {@link dockerCapture}. */
|
|
22
|
+
export interface DockerCaptureResult {
|
|
23
|
+
readonly code: number | null;
|
|
24
|
+
readonly stdout: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Run a docker CLI command and discard its stdio. Resolves on exit
|
|
28
|
+
* code 0 (or always when `allowFailure` is set); rejects otherwise.
|
|
29
|
+
*/
|
|
30
|
+
export declare function dockerExec(args: readonly string[], options?: DockerExecOptions): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Run a docker CLI command and capture stdout. Never throws on non-zero
|
|
33
|
+
* exit; callers inspect the returned `code`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function dockerCapture(args: readonly string[]): Promise<DockerCaptureResult>;
|
|
36
|
+
/** Whether a container with the given name is currently running. */
|
|
37
|
+
export declare function isContainerRunning(name: string): Promise<boolean>;
|
|
38
|
+
/** Whether a Docker network with the given name already exists. */
|
|
39
|
+
export declare function isNetworkPresent(name: string): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Whether a Docker image with the given reference is present in the
|
|
42
|
+
* local image store. Used by the pull-mode image lifecycle to avoid
|
|
43
|
+
* re-pulling a version-pinned image that's already local.
|
|
44
|
+
*
|
|
45
|
+
* @param ref Image reference (tag or registry-qualified `name:tag`).
|
|
46
|
+
* @returns true if `docker image inspect` succeeds.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isImagePresent(ref: string): Promise<boolean>;
|
|
49
|
+
/** Options for {@link buildNetworkCreateArgs} / {@link ensureNetwork}. */
|
|
50
|
+
export interface NetworkCreateOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Create the network with `--internal` so containers on it have no
|
|
53
|
+
* egress to the host or the internet — only to other containers on
|
|
54
|
+
* the same network. Used for the agent's `familiar-isolated` net.
|
|
55
|
+
*/
|
|
56
|
+
readonly internal?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build the `docker network create` argument vector. Pure (no side
|
|
60
|
+
* effects) so it can be unit-tested without a daemon.
|
|
61
|
+
*
|
|
62
|
+
* @param name Network name to create.
|
|
63
|
+
* @param options Network creation flags (e.g. `internal`).
|
|
64
|
+
* @returns The full docker CLI argv, e.g. `["network", "create", "--internal", "familiar-isolated"]`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function buildNetworkCreateArgs(name: string, options?: NetworkCreateOptions): string[];
|
|
67
|
+
/**
|
|
68
|
+
* Create the named bridge network if it doesn't already exist. Idempotent.
|
|
69
|
+
*
|
|
70
|
+
* @param name Network name to ensure.
|
|
71
|
+
* @param options Network creation flags applied only on first creation
|
|
72
|
+
* (an existing network is left as-is).
|
|
73
|
+
*/
|
|
74
|
+
export declare function ensureNetwork(name: string, options?: NetworkCreateOptions): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Attach an already-running container to an additional network by name.
|
|
77
|
+
* Callers recreate their container on every `start()` (via
|
|
78
|
+
* {@link removeContainer} first), so the attachment is always fresh —
|
|
79
|
+
* this throws on failure rather than swallowing it, so a genuine
|
|
80
|
+
* problem (missing network, missing container) surfaces instead of
|
|
81
|
+
* silently leaving the container unable to reach its peer.
|
|
82
|
+
*
|
|
83
|
+
* @param network Network to connect the container to.
|
|
84
|
+
* @param container Name of the running container to attach.
|
|
85
|
+
* @throws If the docker command fails (network/container missing, etc.).
|
|
86
|
+
*/
|
|
87
|
+
export declare function connectNetwork(network: string, container: string): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Force-remove a container by name. Default `allowFailure: true` because
|
|
90
|
+
* "remove if it exists" is the common idempotent-cleanup intent.
|
|
91
|
+
*/
|
|
92
|
+
export declare function removeContainer(name: string, options?: DockerExecOptions): Promise<void>;
|
|
93
|
+
/** Send `docker stop` to a container. Ignores already-stopped/missing containers. */
|
|
94
|
+
export declare function stopContainer(name: string): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Run a docker CLI command with the parent process's stdin/stdout/
|
|
97
|
+
* stderr inherited, blocking until the child exits. Used by
|
|
98
|
+
* subcommands that hand the user's terminal to a foreground docker
|
|
99
|
+
* process (`familiar psql`'s psql shell, `familiar mcp call`'s
|
|
100
|
+
* one-shot CLI invocations against an MCP runtime container).
|
|
101
|
+
*
|
|
102
|
+
* Returns the child's exit status — `null`-on-signal collapses to
|
|
103
|
+
* 1 so the caller's `process.exit(...)` always sees a usable
|
|
104
|
+
* number. A failure to launch docker itself (e.g. binary missing)
|
|
105
|
+
* surfaces as a thrown {@link Error} the caller can format.
|
|
106
|
+
*
|
|
107
|
+
* @throws If the docker binary couldn't be spawned at all.
|
|
108
|
+
*/
|
|
109
|
+
export declare function dockerInteractive(args: readonly string[]): number;
|
|
110
|
+
/**
|
|
111
|
+
* On Linux, `host.docker.internal` isn't built-in and must be mapped to
|
|
112
|
+
* the host gateway explicitly. macOS and Windows resolve it natively.
|
|
113
|
+
*
|
|
114
|
+
* Used by sidecars that dial a host-side listener (the bastion bridge,
|
|
115
|
+
* the tailscale plugin's serve proxy to the webserver).
|
|
116
|
+
*
|
|
117
|
+
* @returns Extra `docker run` args, or an empty array on macOS/Windows.
|
|
118
|
+
*/
|
|
119
|
+
export declare function hostGatewayArgs(): string[];
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
/**
|
|
4
|
+
* Shared bridge network all Familiar containers join. Owned
|
|
5
|
+
* by the daemon (see `commands/Start.ts`); created once via
|
|
6
|
+
* {@link ensureNetwork} before any container is started.
|
|
7
|
+
*/
|
|
8
|
+
export const SHARED_NETWORK_NAME = "familiar-net";
|
|
9
|
+
/**
|
|
10
|
+
* Internal (egress-less) bridge network the locked-down agent container
|
|
11
|
+
* joins. Created with `--internal` so the agent has no route to the
|
|
12
|
+
* internet; it reaches postgres (dual-homed onto this net) and the host
|
|
13
|
+
* bastion (via the `familiar-bastion-bridge` socat sidecar, also on this
|
|
14
|
+
* net) by container name. Owned by the daemon, created via
|
|
15
|
+
* {@link ensureNetwork} with `{ internal: true }`.
|
|
16
|
+
*/
|
|
17
|
+
export const ISOLATED_NETWORK_NAME = "familiar-isolated";
|
|
18
|
+
/**
|
|
19
|
+
* Run a docker CLI command and discard its stdio. Resolves on exit
|
|
20
|
+
* code 0 (or always when `allowFailure` is set); rejects otherwise.
|
|
21
|
+
*/
|
|
22
|
+
export function dockerExec(args, options = {}) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const proc = spawn("docker", [...args], { stdio: ["ignore", "ignore", "pipe"] });
|
|
25
|
+
let stderr = "";
|
|
26
|
+
proc.stderr.on("data", (chunk) => {
|
|
27
|
+
stderr += chunk.toString();
|
|
28
|
+
});
|
|
29
|
+
proc.on("close", (code) => {
|
|
30
|
+
if (code === 0 || options.allowFailure) {
|
|
31
|
+
resolve();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const trimmed = stderr.trim();
|
|
35
|
+
const suffix = trimmed.length > 0 ? `: ${trimmed}` : "";
|
|
36
|
+
reject(new Error(`docker ${args.join(" ")} exited with code ${code}${suffix}`));
|
|
37
|
+
});
|
|
38
|
+
proc.on("error", reject);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run a docker CLI command and capture stdout. Never throws on non-zero
|
|
43
|
+
* exit; callers inspect the returned `code`.
|
|
44
|
+
*/
|
|
45
|
+
export function dockerCapture(args) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const proc = spawn("docker", [...args], { stdio: ["ignore", "pipe", "ignore"] });
|
|
48
|
+
let stdout = "";
|
|
49
|
+
proc.stdout.on("data", (chunk) => {
|
|
50
|
+
stdout += chunk.toString();
|
|
51
|
+
});
|
|
52
|
+
proc.on("close", (code) => {
|
|
53
|
+
resolve({ code, stdout });
|
|
54
|
+
});
|
|
55
|
+
proc.on("error", reject);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** Whether a container with the given name is currently running. */
|
|
59
|
+
export async function isContainerRunning(name) {
|
|
60
|
+
const { code, stdout } = await dockerCapture([
|
|
61
|
+
"container",
|
|
62
|
+
"inspect",
|
|
63
|
+
"-f",
|
|
64
|
+
"{{.State.Running}}",
|
|
65
|
+
name,
|
|
66
|
+
]);
|
|
67
|
+
return code === 0 && stdout.trim() === "true";
|
|
68
|
+
}
|
|
69
|
+
/** Whether a Docker network with the given name already exists. */
|
|
70
|
+
export async function isNetworkPresent(name) {
|
|
71
|
+
const { code } = await dockerCapture(["network", "inspect", name]);
|
|
72
|
+
return code === 0;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Whether a Docker image with the given reference is present in the
|
|
76
|
+
* local image store. Used by the pull-mode image lifecycle to avoid
|
|
77
|
+
* re-pulling a version-pinned image that's already local.
|
|
78
|
+
*
|
|
79
|
+
* @param ref Image reference (tag or registry-qualified `name:tag`).
|
|
80
|
+
* @returns true if `docker image inspect` succeeds.
|
|
81
|
+
*/
|
|
82
|
+
export async function isImagePresent(ref) {
|
|
83
|
+
const { code } = await dockerCapture(["image", "inspect", ref]);
|
|
84
|
+
return code === 0;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build the `docker network create` argument vector. Pure (no side
|
|
88
|
+
* effects) so it can be unit-tested without a daemon.
|
|
89
|
+
*
|
|
90
|
+
* @param name Network name to create.
|
|
91
|
+
* @param options Network creation flags (e.g. `internal`).
|
|
92
|
+
* @returns The full docker CLI argv, e.g. `["network", "create", "--internal", "familiar-isolated"]`.
|
|
93
|
+
*/
|
|
94
|
+
export function buildNetworkCreateArgs(name, options = {}) {
|
|
95
|
+
const args = ["network", "create"];
|
|
96
|
+
if (options.internal) {
|
|
97
|
+
args.push("--internal");
|
|
98
|
+
}
|
|
99
|
+
args.push(name);
|
|
100
|
+
return args;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create the named bridge network if it doesn't already exist. Idempotent.
|
|
104
|
+
*
|
|
105
|
+
* @param name Network name to ensure.
|
|
106
|
+
* @param options Network creation flags applied only on first creation
|
|
107
|
+
* (an existing network is left as-is).
|
|
108
|
+
*/
|
|
109
|
+
export async function ensureNetwork(name, options = {}) {
|
|
110
|
+
if (!(await isNetworkPresent(name))) {
|
|
111
|
+
await dockerExec(buildNetworkCreateArgs(name, options));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Attach an already-running container to an additional network by name.
|
|
116
|
+
* Callers recreate their container on every `start()` (via
|
|
117
|
+
* {@link removeContainer} first), so the attachment is always fresh —
|
|
118
|
+
* this throws on failure rather than swallowing it, so a genuine
|
|
119
|
+
* problem (missing network, missing container) surfaces instead of
|
|
120
|
+
* silently leaving the container unable to reach its peer.
|
|
121
|
+
*
|
|
122
|
+
* @param network Network to connect the container to.
|
|
123
|
+
* @param container Name of the running container to attach.
|
|
124
|
+
* @throws If the docker command fails (network/container missing, etc.).
|
|
125
|
+
*/
|
|
126
|
+
export function connectNetwork(network, container) {
|
|
127
|
+
return dockerExec(["network", "connect", network, container]);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Force-remove a container by name. Default `allowFailure: true` because
|
|
131
|
+
* "remove if it exists" is the common idempotent-cleanup intent.
|
|
132
|
+
*/
|
|
133
|
+
export function removeContainer(name, options = { allowFailure: true }) {
|
|
134
|
+
return dockerExec(["rm", "-f", name], options);
|
|
135
|
+
}
|
|
136
|
+
/** Send `docker stop` to a container. Ignores already-stopped/missing containers. */
|
|
137
|
+
export function stopContainer(name) {
|
|
138
|
+
return dockerExec(["stop", name], { allowFailure: true });
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Run a docker CLI command with the parent process's stdin/stdout/
|
|
142
|
+
* stderr inherited, blocking until the child exits. Used by
|
|
143
|
+
* subcommands that hand the user's terminal to a foreground docker
|
|
144
|
+
* process (`familiar psql`'s psql shell, `familiar mcp call`'s
|
|
145
|
+
* one-shot CLI invocations against an MCP runtime container).
|
|
146
|
+
*
|
|
147
|
+
* Returns the child's exit status — `null`-on-signal collapses to
|
|
148
|
+
* 1 so the caller's `process.exit(...)` always sees a usable
|
|
149
|
+
* number. A failure to launch docker itself (e.g. binary missing)
|
|
150
|
+
* surfaces as a thrown {@link Error} the caller can format.
|
|
151
|
+
*
|
|
152
|
+
* @throws If the docker binary couldn't be spawned at all.
|
|
153
|
+
*/
|
|
154
|
+
export function dockerInteractive(args) {
|
|
155
|
+
const result = spawnSync("docker", [...args], { stdio: "inherit" });
|
|
156
|
+
if (result.error) {
|
|
157
|
+
throw new Error(`failed to launch docker: ${result.error.message}`);
|
|
158
|
+
}
|
|
159
|
+
return result.status ?? 1;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* On Linux, `host.docker.internal` isn't built-in and must be mapped to
|
|
163
|
+
* the host gateway explicitly. macOS and Windows resolve it natively.
|
|
164
|
+
*
|
|
165
|
+
* Used by sidecars that dial a host-side listener (the bastion bridge,
|
|
166
|
+
* the tailscale plugin's serve proxy to the webserver).
|
|
167
|
+
*
|
|
168
|
+
* @returns Extra `docker run` args, or an empty array on macOS/Windows.
|
|
169
|
+
*/
|
|
170
|
+
export function hostGatewayArgs() {
|
|
171
|
+
if (os.platform() === "linux") {
|
|
172
|
+
return ["--add-host=host.docker.internal:host-gateway"];
|
|
173
|
+
}
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defaults of the core `webserver.*` config keys. Shared because both
|
|
3
|
+
* the host's `WebServer` and plugins that front it (tailscale) must
|
|
4
|
+
* resolve the same effective port and bind address.
|
|
5
|
+
*/
|
|
6
|
+
/** Port the webserver listens on when `webserver.port` is unset. */
|
|
7
|
+
export declare const DEFAULT_WEBSERVER_PORT = 8080;
|
|
8
|
+
/** Address the webserver binds on when `webserver.host` is unset. */
|
|
9
|
+
export declare const DEFAULT_WEBSERVER_HOST = "0.0.0.0";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defaults of the core `webserver.*` config keys. Shared because both
|
|
3
|
+
* the host's `WebServer` and plugins that front it (tailscale) must
|
|
4
|
+
* resolve the same effective port and bind address.
|
|
5
|
+
*/
|
|
6
|
+
/** Port the webserver listens on when `webserver.port` is unset. */
|
|
7
|
+
export const DEFAULT_WEBSERVER_PORT = 8080;
|
|
8
|
+
/** Address the webserver binds on when `webserver.host` is unset. */
|
|
9
|
+
export const DEFAULT_WEBSERVER_HOST = "0.0.0.0";
|
package/build/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { type ChatHandler, ChatMessageBus, type ChatUnsubscribe } from "./ChatMe
|
|
|
7
7
|
export { type ConfigService, DEFAULT_WRITABLE_PATHS } from "./Config.js";
|
|
8
8
|
export type { ContainerToolInfo } from "./ContainerTool.js";
|
|
9
9
|
export { type ParsedCron, parseCron } from "./CronExpression.js";
|
|
10
|
+
export { buildNetworkCreateArgs, connectNetwork, type DockerCaptureResult, type DockerExecOptions, dockerCapture, dockerExec, dockerInteractive, ensureNetwork, hostGatewayArgs, ISOLATED_NETWORK_NAME, isContainerRunning, isImagePresent, isNetworkPresent, type NetworkCreateOptions, removeContainer, SHARED_NETWORK_NAME, stopContainer, } from "./DockerTools.js";
|
|
10
11
|
export type { EventFile, EventFilter, EventPatch, EventRow, EventState, NewEvent, } from "./Event.js";
|
|
11
12
|
export { EVENT_PRIORITY } from "./Event.js";
|
|
12
13
|
export { DuplicateIdempotencyKeyError, EventBus } from "./EventBus.js";
|
|
@@ -40,3 +41,4 @@ export { DEFAULT_TOOL_LEVEL, type ToolLevel } from "./ToolLevel.js";
|
|
|
40
41
|
export { DEFAULT_TOOL_CALL_OFFLOADING_LIMIT, type OffloadedJson, runJsonLinesTool, runJsonTool, runTextTool, ToolError, type ToolRunContext, type ToolRunOptions, truncateUtf8, } from "./ToolRunner.js";
|
|
41
42
|
export { ALL_GROUP_NAME, CORE_PLUGIN_ID, IDENT_PATTERN, MCP_GROUP_NAME, matchesAnyToolPattern, NONE_GROUP_NAME, RESERVED_GROUP_NAMES, toolPatternMatches, validateGroupName, } from "./ToolsExpressionUtils.js";
|
|
42
43
|
export type { WorkspaceFile, WorkspaceFileFilter, WorkspaceWatcherApi, } from "./WorkspaceFile.js";
|
|
44
|
+
export { DEFAULT_WEBSERVER_HOST, DEFAULT_WEBSERVER_PORT } from "./WebServerDefaults.js";
|
package/build/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { buildCalendarEventId, parseCalendarEventId, } from "./Calendar.js";
|
|
|
4
4
|
export { ChatMessageBus } from "./ChatMessageBus.js";
|
|
5
5
|
export { DEFAULT_WRITABLE_PATHS } from "./Config.js";
|
|
6
6
|
export { parseCron } from "./CronExpression.js";
|
|
7
|
+
export { buildNetworkCreateArgs, connectNetwork, dockerCapture, dockerExec, dockerInteractive, ensureNetwork, hostGatewayArgs, ISOLATED_NETWORK_NAME, isContainerRunning, isImagePresent, isNetworkPresent, removeContainer, SHARED_NETWORK_NAME, stopContainer, } from "./DockerTools.js";
|
|
7
8
|
export { EVENT_PRIORITY } from "./Event.js";
|
|
8
9
|
export { DuplicateIdempotencyKeyError, EventBus } from "./EventBus.js";
|
|
9
10
|
export { estimateTokens } from "./estimateTokens.js";
|
|
@@ -30,3 +31,4 @@ export { ToolCallBus } from "./ToolCallBus.js";
|
|
|
30
31
|
export { DEFAULT_TOOL_LEVEL } from "./ToolLevel.js";
|
|
31
32
|
export { DEFAULT_TOOL_CALL_OFFLOADING_LIMIT, runJsonLinesTool, runJsonTool, runTextTool, ToolError, truncateUtf8, } from "./ToolRunner.js";
|
|
32
33
|
export { ALL_GROUP_NAME, CORE_PLUGIN_ID, IDENT_PATTERN, MCP_GROUP_NAME, matchesAnyToolPattern, NONE_GROUP_NAME, RESERVED_GROUP_NAMES, toolPatternMatches, validateGroupName, } from "./ToolsExpressionUtils.js";
|
|
34
|
+
export { DEFAULT_WEBSERVER_HOST, DEFAULT_WEBSERVER_PORT } from "./WebServerDefaults.js";
|