@falkenslab/agent-kit 0.27.0 → 0.27.1

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.
@@ -47,8 +47,12 @@ async function packageBin(dir, name) {
47
47
  const bundled = json.bundleDependencies ?? json.bundledDependencies;
48
48
  const declared = Object.keys({ ...json.dependencies, ...json.optionalDependencies });
49
49
  const missing = bundled === true ? [] : declared.filter((dependency) => !(Array.isArray(bundled) && bundled.includes(dependency)));
50
+ // Only package.json is read (#71): one bundled into the program but still in `dependencies`
51
+ // looks missing too, so the message says both ways out.
50
52
  if (missing.length)
51
- return { problem: `has dependencies it doesn't bring (${missing.join(", ")}): the kit installs none, so a package must bundle them, or list them in bundleDependencies` };
53
+ return {
54
+ problem: `declares dependencies it doesn't bring (${missing.join(", ")}): the kit installs none. If they're bundled into its program, move them to devDependencies; otherwise list them in bundleDependencies`,
55
+ };
52
56
  const scripts = ["preinstall", "install", "postinstall"].filter((script) => json.scripts?.[script]);
53
57
  if (scripts.length)
54
58
  return { problem: `has install scripts (${scripts.join(", ")}), which the kit never runs` };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Whether a browser can be opened on this computer (#72): not over SSH, nor on Linux without a
3
+ * display (a server, a container). Elsewhere the desktop is there.
4
+ */
5
+ export declare function canOpenBrowser(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
6
+ /**
7
+ * Opens an `http(s)` address in this computer's browser, with the system's own opener and no
8
+ * shell (an OAuth link is full of `&`, which `cmd /c start` would cut): `rundll32` on Windows,
9
+ * `open` on macOS, `xdg-open` elsewhere. False when it can't (`canOpenBrowser()`, another kind
10
+ * of address, no opener): then the person is given the link only.
11
+ */
12
+ export declare function openBrowser(url: string): Promise<boolean>;
@@ -0,0 +1,36 @@
1
+ import { spawn } from "node:child_process";
2
+ /**
3
+ * Whether a browser can be opened on this computer (#72): not over SSH, nor on Linux without a
4
+ * display (a server, a container). Elsewhere the desktop is there.
5
+ */
6
+ export function canOpenBrowser(env = process.env, platform = process.platform) {
7
+ if (env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT)
8
+ return false;
9
+ if (platform === "win32" || platform === "darwin")
10
+ return true;
11
+ return Boolean(env.DISPLAY || env.WAYLAND_DISPLAY);
12
+ }
13
+ /**
14
+ * Opens an `http(s)` address in this computer's browser, with the system's own opener and no
15
+ * shell (an OAuth link is full of `&`, which `cmd /c start` would cut): `rundll32` on Windows,
16
+ * `open` on macOS, `xdg-open` elsewhere. False when it can't (`canOpenBrowser()`, another kind
17
+ * of address, no opener): then the person is given the link only.
18
+ */
19
+ export async function openBrowser(url) {
20
+ if (!/^https?:\/\//i.test(url) || !canOpenBrowser())
21
+ return false;
22
+ const [command, args] = process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
23
+ return await new Promise((resolve) => {
24
+ try {
25
+ const child = spawn(command, args, { detached: true, stdio: "ignore", windowsHide: true });
26
+ child.once("error", () => resolve(false));
27
+ child.once("spawn", () => {
28
+ child.unref();
29
+ resolve(true);
30
+ });
31
+ }
32
+ catch {
33
+ resolve(false);
34
+ }
35
+ });
36
+ }
@@ -59,8 +59,10 @@ export async function runExtensionCommand(argv, options) {
59
59
  const ask = options.ask ?? askOnTerminal;
60
60
  const askSecret = options.askSecret ?? (options.ask ? options.ask : askSecretOnTerminal);
61
61
  /**
62
- * After installing `name` into `dir`: its options from `--set`, then asked for each still
63
- * without a value (on a terminal; none without one), saying which required ones are left.
62
+ * After installing `name` into `dir`: its options from `--set`, saved first so a prompt cut
63
+ * short loses none (#73), then asked for each still without a value, saying which required ones
64
+ * are left. Never asked with `--yes`, nor without a terminal unless the host asks itself (`ask`):
65
+ * a default stays, an optional one stays unset.
64
66
  */
65
67
  const configure = async (name, dir) => {
66
68
  const installed = (await listInstalled(options.dirs)).find((extension) => extension.name === name && extension.scopeDir === dir);
@@ -74,17 +76,24 @@ export async function runExtensionCommand(argv, options) {
74
76
  throw new Error(`--set takes key=value: "${pair}"`);
75
77
  typed[pair.slice(0, at)] = pair.slice(at + 1);
76
78
  }
79
+ if (Object.keys(typed).length)
80
+ await setExtensionOptions(installed, name, typed);
77
81
  const states = await describeOptionStates(installed);
82
+ const canAsk = !flag("yes") && (options.ask !== undefined || process.stdin.isTTY === true);
83
+ const asked = {};
78
84
  for (const [key, option] of Object.entries(declared)) {
85
+ if (!canAsk)
86
+ break;
79
87
  if (key in typed || states.get(key))
80
88
  continue;
81
89
  const question = `${option.title}${option.required ? "" : " (optional)"}${option.default !== undefined ? ` [${String(option.default)}]` : ""}: ${option.description}\n ${key} = `;
82
90
  const answer = (await (option.sensitive ? askSecret : ask)(question)).trim();
83
91
  if (answer)
84
- typed[key] = answer;
92
+ asked[key] = answer;
85
93
  }
86
- if (Object.keys(typed).length)
87
- await setExtensionOptions(installed, name, typed);
94
+ if (Object.keys(asked).length)
95
+ await setExtensionOptions(installed, name, asked);
96
+ Object.assign(typed, asked);
88
97
  const missing = Object.entries(declared).filter(([key, option]) => option.required && !(key in typed) && !states.get(key) && option.default === undefined).map(([key]) => key);
89
98
  if (missing.length)
90
99
  write(`It stays off until ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} set: \`${command} extension config ${name} ${missing[0]}\`.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falkenslab/agent-kit",
3
- "version": "0.27.0",
3
+ "version": "0.27.1",
4
4
  "description": "Library for building an agent on top of the Claude Agent SDK",
5
5
  "author": "Francisco Vargas Ruiz",
6
6
  "license": "MIT",