@vincemakes/kiso-tools-node 0.34.0 → 0.36.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/dist/index.d.ts CHANGED
@@ -100,6 +100,12 @@ export interface WorkspaceToolsOptions {
100
100
  * name — explicit beats the heuristic, as it does for MCP servers.
101
101
  */
102
102
  readonly shellEnv?: "inherit" | Readonly<Record<string, string>>;
103
+ /** Astra F7: the env var NAMES the configured profiles authenticate with.
104
+ * The strip's suffix rules (`_API_KEY`, `_AUTH_TOKEN`) cannot see a name
105
+ * like `REVIEW_PROVIDER_TOKEN`, and only the config knows which names are
106
+ * secrets — so it says so. Ignored when `shellEnv` is "inherit", which is
107
+ * an explicit opt-in to the whole environment. */
108
+ readonly secretEnvNames?: readonly string[];
103
109
  /**
104
110
  * DC-54 — the bounds that keep a tool call finite. Every field is
105
111
  * optional and defaults to the constant beside it; a host embedding
@@ -166,6 +172,7 @@ export declare function editFileTool(opts: WorkspaceToolsOptions): Tool<{
166
172
  }[];
167
173
  expectedRevision?: string;
168
174
  }>;
175
+ export { SHELL_STRIP_EXACT, strippedShellEnv } from "./secret-env.js";
169
176
  export declare function shellTool(opts: WorkspaceToolsOptions): Tool<{
170
177
  command: string;
171
178
  timeoutMs?: number;
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ import { tmpdir } from "node:os";
27
27
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
28
28
  import { defineTool } from "@vincemakes/kiso-core";
29
29
  // WR-1/WR-1A — the revision-guard primitives (unit-tested in wr1a-coda):
30
+ import { strippedShellEnv } from "./secret-env.js";
30
31
  import { contentRevision, normalizeRevision, postEffectEscape, precondition, publishNewFile, revalidateBeforeRename } from "./wr1.js";
31
32
  /**
32
33
  * TUI2-R1 (C) — THE SHELL PROGRESS SIDECAR.
@@ -995,37 +996,15 @@ export function editFileTool(opts) {
995
996
  },
996
997
  });
997
998
  }
998
- /**
999
- * bootstrap #3 (finding #7): the explicit credential list stripped from shell
1000
- * children — the agent's own provider surface (both families' keys, base
1001
- * URLs, and model choices) plus the generic API-key / auth-token patterns
1002
- * that cover other providers. Everything else in the environment passes
1003
- * through untouched.
1004
- */
1005
- const SHELL_STRIP_EXACT = new Set([
1006
- "ANTHROPIC_API_KEY",
1007
- "OPENAI_API_KEY",
1008
- "ANTHROPIC_BASE_URL",
1009
- "OPENAI_BASE_URL",
1010
- "ANTHROPIC_MODEL",
1011
- "OPENAI_MODEL",
1012
- ]);
1013
- function strippedShellEnv(env) {
1014
- const out = {};
1015
- for (const [key, value] of Object.entries(env)) {
1016
- if (SHELL_STRIP_EXACT.has(key))
1017
- continue;
1018
- if (key.endsWith("_API_KEY") || key.endsWith("_AUTH_TOKEN"))
1019
- continue;
1020
- if (value !== undefined)
1021
- out[key] = value;
1022
- }
1023
- return out;
1024
- }
999
+ // Astra F7: the strip is ONE implementation, shared with the MCP
1000
+ // extension's stdio children (./secret-env.ts). It used to live here
1001
+ // with a hand-kept copy over there; the copy never learned the
1002
+ // declared names, which is how MCP children kept leaking.
1003
+ export { SHELL_STRIP_EXACT, strippedShellEnv } from "./secret-env.js";
1025
1004
  export function shellTool(opts) {
1026
1005
  return defineTool({
1027
1006
  name: "shell",
1028
- description: "Run a shell command with the workspace as the working directory. A side effect — approval required. Fails loudly on timeout or non-zero exit.",
1007
+ description: "Run a shell command through /bin/sh with the workspace root as the working directory: builds, tests, git, package managers, curl for HTTP APIs, system queries. Side effects are real; the human may be asked to approve the run. Fails loudly on timeout or non-zero exit.",
1029
1008
  parameters: {
1030
1009
  type: "object",
1031
1010
  properties: {
@@ -1035,7 +1014,7 @@ export function shellTool(opts) {
1035
1014
  required: ["command"],
1036
1015
  additionalProperties: false,
1037
1016
  },
1038
- promptSnippet: "shell — real system commands only (builds, tests, git)",
1017
+ promptSnippet: "shell — any command the task needs (builds, tests, git, curl, system queries)",
1039
1018
  promptGuidelines: ["commands run in the workspace root; on failure read the error and adjust — never repeat blindly"],
1040
1019
  execute: async ({ command, timeoutMs }, ctx) => {
1041
1020
  const timeout = timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
@@ -1058,7 +1037,7 @@ export function shellTool(opts) {
1058
1037
  stdio: ["ignore", "pipe", "pipe"],
1059
1038
  env: opts.shellEnv === "inherit"
1060
1039
  ? process.env
1061
- : { ...strippedShellEnv(process.env), ...(opts.shellEnv ?? {}) },
1040
+ : { ...strippedShellEnv(process.env, opts.secretEnvNames), ...(opts.shellEnv ?? {}) },
1062
1041
  });
1063
1042
  let stdout = "";
1064
1043
  let stderr = "";
@@ -0,0 +1,27 @@
1
+ /**
2
+ * THE child-environment strip — ONE implementation, both children.
3
+ *
4
+ * bootstrap #3 (finding #7) established the rule for shell children: the
5
+ * agent's own provider surface (both families' keys, base URLs and model
6
+ * choices) plus the generic API-key / auth-token patterns that cover other
7
+ * providers. Everything else passes through untouched.
8
+ *
9
+ * Astra F7 then showed the rule was incomplete AND duplicated. Incomplete:
10
+ * a profile's `apiKeyEnv` can be ANY name — `REVIEW_PROVIDER_TOKEN` ends in
11
+ * neither suffix — so the key a profile authenticates with survived into
12
+ * the child. Duplicated: the MCP extension carried its own copy of the list
13
+ * with a "keep in sync" comment, and a comment is not a mechanism; the copy
14
+ * did not learn the declared names and MCP stdio children kept leaking.
15
+ *
16
+ * The names must come from the caller because only the config knows which
17
+ * env vars are secrets. No names declared = exactly the pre-F7 behaviour,
18
+ * which is what a standalone extension load gets.
19
+ *
20
+ * This module imports NOTHING on purpose: the MCP extension is an esbuild
21
+ * bundle, and a shared rule must not drag a package graph across with it.
22
+ */
23
+ /** The explicit credential list stripped from every child. */
24
+ export declare const SHELL_STRIP_EXACT: ReadonlySet<string>;
25
+ /** The environment a child process may see: `env` minus the exact list,
26
+ * minus every DECLARED secret name, minus the two suffix families. */
27
+ export declare function strippedShellEnv(env: Record<string, string | undefined>, secretNames?: readonly string[]): Record<string, string | undefined>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * THE child-environment strip — ONE implementation, both children.
3
+ *
4
+ * bootstrap #3 (finding #7) established the rule for shell children: the
5
+ * agent's own provider surface (both families' keys, base URLs and model
6
+ * choices) plus the generic API-key / auth-token patterns that cover other
7
+ * providers. Everything else passes through untouched.
8
+ *
9
+ * Astra F7 then showed the rule was incomplete AND duplicated. Incomplete:
10
+ * a profile's `apiKeyEnv` can be ANY name — `REVIEW_PROVIDER_TOKEN` ends in
11
+ * neither suffix — so the key a profile authenticates with survived into
12
+ * the child. Duplicated: the MCP extension carried its own copy of the list
13
+ * with a "keep in sync" comment, and a comment is not a mechanism; the copy
14
+ * did not learn the declared names and MCP stdio children kept leaking.
15
+ *
16
+ * The names must come from the caller because only the config knows which
17
+ * env vars are secrets. No names declared = exactly the pre-F7 behaviour,
18
+ * which is what a standalone extension load gets.
19
+ *
20
+ * This module imports NOTHING on purpose: the MCP extension is an esbuild
21
+ * bundle, and a shared rule must not drag a package graph across with it.
22
+ */
23
+ /** The explicit credential list stripped from every child. */
24
+ export const SHELL_STRIP_EXACT = new Set([
25
+ "ANTHROPIC_API_KEY",
26
+ "OPENAI_API_KEY",
27
+ "ANTHROPIC_BASE_URL",
28
+ "OPENAI_BASE_URL",
29
+ "ANTHROPIC_MODEL",
30
+ "OPENAI_MODEL",
31
+ ]);
32
+ /** The environment a child process may see: `env` minus the exact list,
33
+ * minus every DECLARED secret name, minus the two suffix families. */
34
+ export function strippedShellEnv(env, secretNames = []) {
35
+ const declared = new Set(secretNames);
36
+ const out = {};
37
+ for (const [key, value] of Object.entries(env)) {
38
+ if (SHELL_STRIP_EXACT.has(key))
39
+ continue;
40
+ if (declared.has(key))
41
+ continue;
42
+ if (key.endsWith("_API_KEY") || key.endsWith("_AUTH_TOKEN"))
43
+ continue;
44
+ if (value !== undefined)
45
+ out[key] = value;
46
+ }
47
+ return out;
48
+ }
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tools-node",
3
- "version": "0.34.0",
4
- "description": "kiso coding tools for Node hosts — read file, list directory, search text, write/edit file, shell command.",
3
+ "version": "0.36.0",
4
+ "description": "kiso coding tools for Node hosts \u2014 read file, list directory, search text, write/edit file, shell command.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "exports": {
8
8
  ".": {
9
9
  "types": "./dist/index.d.ts",
10
10
  "default": "./dist/index.js"
11
+ },
12
+ "./secret-env": {
13
+ "types": "./dist/secret-env.d.ts",
14
+ "default": "./dist/secret-env.js"
11
15
  }
12
16
  },
13
17
  "files": [
@@ -21,7 +25,7 @@
21
25
  "test": "vitest run"
22
26
  },
23
27
  "dependencies": {
24
- "@vincemakes/kiso-core": "0.34.0"
28
+ "@vincemakes/kiso-core": "0.36.0"
25
29
  },
26
30
  "devDependencies": {
27
31
  "@types/node": "^26.1.2",