@flue/sdk 0.1.2 → 0.1.3

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 CHANGED
@@ -94,9 +94,8 @@ A triage agent that runs in CI whenever an issue is opened on GitHub. The `"loca
94
94
 
95
95
  ```ts
96
96
  // .flue/agents/triage.ts
97
- import { defineCommand, type FlueContext } from '@flue/sdk/client';
98
- import { execFile } from 'node:child_process';
99
- import { promisify } from 'node:util';
97
+ import { type FlueContext } from '@flue/sdk/client';
98
+ import { defineCommand } from '@flue/sdk/node';
100
99
  import * as v from 'valibot';
101
100
 
102
101
  // Because we are running this in CI, we don't need to expose this as an HTTP endpoint.
@@ -106,12 +105,8 @@ export const triggers = {};
106
105
  // Connect privileged CLIs to your agent without leaking sensitive keys and secrets.
107
106
  // Secrets are hooked up inside the command definition here, so your agent never sees them.
108
107
  // Commands are controlled per-prompt, so you can be as granular with access as you need.
109
- const npm = defineCommand('npm', async (args) => promisify(execFile)('npm', args));
110
- const gh = defineCommand('gh', async (args) =>
111
- promisify(execFile)('gh', args, {
112
- env: { GH_TOKEN: process.env.GH_TOKEN },
113
- }),
114
- );
108
+ const npm = defineCommand('npm');
109
+ const gh = defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } });
115
110
 
116
111
  export default async function ({ init, payload }: FlueContext) {
117
112
  // 'local' mounts the host filesystem at /workspace — ideal for CI
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ShellOptions, D as TaskOptions, E as SkillOptions, O as ToolDef, S as SessionStore, b as SessionEnv, d as FlueContext, f as FlueEvent, g as PromptResponse, h as PromptOptions, l as CommandSupport, m as FlueSession, p as FlueEventCallback, r as BashLike, s as Command, t as AgentConfig, u as FileStat, v as SandboxFactory, w as ShellResult, x as SessionInit, y as SessionData } from "./types-C8tsaK1j.mjs";
1
+ import { C as ShellOptions, D as TaskOptions, E as SkillOptions, O as ToolDef, S as SessionStore, b as SessionEnv, d as FlueContext, f as FlueEvent, g as PromptResponse, h as PromptOptions, l as CommandSupport, m as FlueSession, p as FlueEventCallback, r as BashLike, s as Command, t as AgentConfig, u as FileStat, v as SandboxFactory, w as ShellResult, x as SessionInit, y as SessionData } from "./types-BZPltYah.mjs";
2
2
  import { Type } from "@mariozechner/pi-ai";
3
3
 
4
4
  //#region src/client.d.ts
@@ -21,10 +21,5 @@ interface FlueContextInternal extends FlueContext {
21
21
  setEventCallback(callback: FlueEventCallback | undefined): void;
22
22
  }
23
23
  declare function createFlueContext(config: FlueContextConfig): FlueContextInternal;
24
- declare function defineCommand(name: string, execute: (args: string[]) => Promise<{
25
- stdout: string;
26
- stderr: string;
27
- exitCode: number;
28
- }>): Command;
29
24
  //#endregion
30
- export { type BashLike, type Command, type CommandSupport, type FileStat, type FlueContext, FlueContextConfig, FlueContextInternal, type FlueEvent, type FlueEventCallback, type FlueSession, type PromptOptions, type PromptResponse, type SandboxFactory, type SessionData, type SessionEnv, type SessionInit, type SessionStore, type ShellOptions, type ShellResult, type SkillOptions, type TaskOptions, type ToolDef, Type, createFlueContext, defineCommand };
25
+ export { type BashLike, type Command, type CommandSupport, type FileStat, type FlueContext, FlueContextConfig, FlueContextInternal, type FlueEvent, type FlueEventCallback, type FlueSession, type PromptOptions, type PromptResponse, type SandboxFactory, type SessionData, type SessionEnv, type SessionInit, type SessionStore, type ShellOptions, type ShellResult, type SkillOptions, type TaskOptions, type ToolDef, Type, createFlueContext };
package/dist/client.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { r as discoverSessionContext } from "./agent-BYG0nVbQ.mjs";
2
- import { n as Session } from "./session-BRLCNVG1.mjs";
2
+ import { n as Session } from "./session-CiAMTsLZ.mjs";
3
3
  import { bashToSessionEnv } from "./sandbox.mjs";
4
4
  import { Type } from "@mariozechner/pi-ai";
5
5
 
@@ -32,7 +32,7 @@ function createFlueContext(config) {
32
32
  skills: localContext.skills,
33
33
  model: sessionModel
34
34
  };
35
- return new Session(config.sessionId, sessionConfig, env, store, savedData, currentEventCallback);
35
+ return new Session(config.sessionId, sessionConfig, env, store, savedData, currentEventCallback, options?.commands);
36
36
  },
37
37
  setEventCallback(callback) {
38
38
  currentEventCallback = callback;
@@ -54,12 +54,6 @@ async function resolveSessionEnv(sessionId, sandbox, config) {
54
54
  }
55
55
  return sandbox.createSessionEnv({ sessionId });
56
56
  }
57
- function defineCommand(name, execute) {
58
- return {
59
- name,
60
- execute
61
- };
62
- }
63
57
 
64
58
  //#endregion
65
- export { Type, createFlueContext, defineCommand };
59
+ export { Type, createFlueContext };
@@ -1,4 +1,5 @@
1
- import { S as SessionStore, b as SessionEnv } from "../types-C8tsaK1j.mjs";
1
+ import { S as SessionStore, b as SessionEnv, s as Command } from "../types-BZPltYah.mjs";
2
+ import { t as CommandExecutor } from "../command-helpers-BPcSV93o.mjs";
2
3
 
3
4
  //#region src/cloudflare/virtual-sandbox.d.ts
4
5
  interface VirtualSandboxOptions {
@@ -8,6 +9,9 @@ interface VirtualSandboxOptions {
8
9
  declare function getVirtualSandbox(): Promise<any>;
9
10
  declare function getVirtualSandbox(bucket: unknown, options?: VirtualSandboxOptions): Promise<any>;
10
11
  //#endregion
12
+ //#region src/cloudflare/define-command.d.ts
13
+ declare function defineCommand(name: string, execute: CommandExecutor): Command;
14
+ //#endregion
11
15
  //#region src/cloudflare/cf-sandbox.d.ts
12
16
  declare function cfSandboxToSessionEnv(sandbox: any, cwd?: string): Promise<SessionEnv>;
13
17
  //#endregion
@@ -33,4 +37,4 @@ declare function setCloudflareContext(ctx: CloudflareContext): void;
33
37
  declare function getCloudflareContext(): CloudflareContext;
34
38
  declare function clearCloudflareContext(): void;
35
39
  //#endregion
36
- export { type CloudflareContext, type VirtualSandboxOptions, cfSandboxToSessionEnv, clearCloudflareContext, getCloudflareContext, getVirtualSandbox, setCloudflareContext, store };
40
+ export { type CloudflareContext, type VirtualSandboxOptions, cfSandboxToSessionEnv, clearCloudflareContext, defineCommand, getCloudflareContext, getVirtualSandbox, setCloudflareContext, store };
@@ -1,6 +1,7 @@
1
1
  import "../agent-BYG0nVbQ.mjs";
2
- import "../session-BRLCNVG1.mjs";
2
+ import "../session-CiAMTsLZ.mjs";
3
3
  import { createSandboxSessionEnv } from "../sandbox.mjs";
4
+ import { t as normalizeExecutor } from "../command-helpers-CxRhK1my.mjs";
4
5
  import { Workspace, WorkspaceFileSystem } from "@cloudflare/shell";
5
6
 
6
7
  //#region src/cloudflare/context.ts
@@ -105,6 +106,28 @@ async function getVirtualSandbox(bucket, options) {
105
106
  });
106
107
  }
107
108
 
109
+ //#endregion
110
+ //#region src/cloudflare/define-command.ts
111
+ /**
112
+ * Cloudflare-specific `defineCommand`. Function form only — Workers cannot
113
+ * spawn host processes, so there is no pass-through sugar. The user supplies
114
+ * an executor (typically `fetch`-based or SDK-based) and benefits from
115
+ * return-shape normalization plus automatic throw-catching.
116
+ *
117
+ * ```ts
118
+ * const issues = defineCommand('issues', async (args) => {
119
+ * const res = await fetch(`https://api.github.com/...`);
120
+ * return { stdout: await res.text() };
121
+ * });
122
+ * ```
123
+ */
124
+ function defineCommand(name, execute) {
125
+ return {
126
+ name,
127
+ execute: normalizeExecutor(execute)
128
+ };
129
+ }
130
+
108
131
  //#endregion
109
132
  //#region src/cloudflare/cf-sandbox.ts
110
133
  /** Wraps a @cloudflare/sandbox instance (from getSandbox()) into SessionEnv. */
@@ -204,4 +227,4 @@ function store() {
204
227
  }
205
228
 
206
229
  //#endregion
207
- export { cfSandboxToSessionEnv, clearCloudflareContext, getCloudflareContext, getVirtualSandbox, setCloudflareContext, store };
230
+ export { cfSandboxToSessionEnv, clearCloudflareContext, defineCommand, getCloudflareContext, getVirtualSandbox, setCloudflareContext, store };
@@ -0,0 +1,21 @@
1
+ import { w as ShellResult } from "./types-BZPltYah.mjs";
2
+
3
+ //#region src/command-helpers.d.ts
4
+ /**
5
+ * Loose return shape accepted from user-supplied command executors. All forms
6
+ * are normalized to a full `ShellResult` by `normalizeExecutor()`.
7
+ */
8
+ type CommandExecutorResult = ShellResult | {
9
+ stdout?: string;
10
+ stderr?: string;
11
+ exitCode?: number;
12
+ } | string | void;
13
+ /**
14
+ * User-supplied command executor. Can return a full `ShellResult`, a partial
15
+ * `{ stdout?, stderr?, exitCode? }` object, a bare string (treated as stdout),
16
+ * or void (empty success). Thrown errors are caught and converted to an
17
+ * `exitCode`-bearing `ShellResult` — no `try`/`catch` needed at the call site.
18
+ */
19
+ type CommandExecutor = (args: string[]) => Promise<CommandExecutorResult>;
20
+ //#endregion
21
+ export { CommandExecutor as t };
@@ -0,0 +1,37 @@
1
+ //#region src/command-helpers.ts
2
+ /**
3
+ * Wrap a user-supplied `CommandExecutor` to always resolve with a full
4
+ * `ShellResult`. Applies loose-return normalization and catches throws.
5
+ */
6
+ function normalizeExecutor(executor) {
7
+ return async (args) => {
8
+ try {
9
+ const raw = await executor(args);
10
+ if (raw === void 0 || raw === null) return {
11
+ stdout: "",
12
+ stderr: "",
13
+ exitCode: 0
14
+ };
15
+ if (typeof raw === "string") return {
16
+ stdout: raw,
17
+ stderr: "",
18
+ exitCode: 0
19
+ };
20
+ return {
21
+ stdout: raw.stdout ?? "",
22
+ stderr: raw.stderr ?? "",
23
+ exitCode: raw.exitCode ?? 0
24
+ };
25
+ } catch (err) {
26
+ const e = err ?? {};
27
+ return {
28
+ stdout: typeof e.stdout === "string" ? e.stdout : "",
29
+ stderr: typeof e.stderr === "string" ? e.stderr : String(err),
30
+ exitCode: typeof e.code === "number" ? e.code : 1
31
+ };
32
+ }
33
+ };
34
+ }
35
+
36
+ //#endregion
37
+ export { normalizeExecutor as t };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ShellOptions, D as TaskOptions, E as SkillOptions, O as ToolDef, S as SessionStore, T as Skill, _ as Role, a as BuildOptions, b as SessionEnv, c as CommandDef, d as FlueContext, f as FlueEvent, g as PromptResponse, h as PromptOptions, i as BuildContext, l as CommandSupport, m as FlueSession, n as AgentInfo, o as BuildPlugin, p as FlueEventCallback, r as BashLike, s as Command, t as AgentConfig, u as FileStat, v as SandboxFactory, w as ShellResult, x as SessionInit, y as SessionData } from "./types-C8tsaK1j.mjs";
1
+ import { C as ShellOptions, D as TaskOptions, E as SkillOptions, O as ToolDef, S as SessionStore, T as Skill, _ as Role, a as BuildOptions, b as SessionEnv, c as CommandDef, d as FlueContext, f as FlueEvent, g as PromptResponse, h as PromptOptions, i as BuildContext, l as CommandSupport, m as FlueSession, n as AgentInfo, o as BuildPlugin, p as FlueEventCallback, r as BashLike, s as Command, t as AgentConfig, u as FileStat, v as SandboxFactory, w as ShellResult, x as SessionInit, y as SessionData } from "./types-BZPltYah.mjs";
2
2
  import { AgentTool } from "@mariozechner/pi-agent-core";
3
3
 
4
4
  //#region src/build.d.ts
@@ -1,4 +1,4 @@
1
- import { S as SessionStore, y as SessionData } from "./types-C8tsaK1j.mjs";
1
+ import { S as SessionStore, y as SessionData } from "./types-BZPltYah.mjs";
2
2
  import { FlueContextConfig, FlueContextInternal, createFlueContext } from "./client.mjs";
3
3
  import { bashToSessionEnv } from "./sandbox.mjs";
4
4
  import "valibot";
package/dist/internal.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./agent-BYG0nVbQ.mjs";
2
- import { t as InMemorySessionStore } from "./session-BRLCNVG1.mjs";
2
+ import { t as InMemorySessionStore } from "./session-CiAMTsLZ.mjs";
3
3
  import { bashToSessionEnv } from "./sandbox.mjs";
4
4
  import { createFlueContext } from "./client.mjs";
5
5
 
@@ -0,0 +1,14 @@
1
+ import { s as Command } from "../types-BZPltYah.mjs";
2
+ import { t as CommandExecutor } from "../command-helpers-BPcSV93o.mjs";
3
+ import { execFile } from "node:child_process";
4
+
5
+ //#region src/node/define-command.d.ts
6
+ /**
7
+ * Options forwarded directly to Node's `child_process.execFile`. Full pass-through.
8
+ */
9
+ type CommandOptions = NonNullable<Parameters<typeof execFile>[2]>;
10
+ declare function defineCommand(name: string): Command;
11
+ declare function defineCommand(name: string, options: CommandOptions): Command;
12
+ declare function defineCommand(name: string, execute: CommandExecutor): Command;
13
+ //#endregion
14
+ export { type CommandOptions, defineCommand };
@@ -0,0 +1,75 @@
1
+ import { t as normalizeExecutor } from "../command-helpers-CxRhK1my.mjs";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+
5
+ //#region src/node/define-command.ts
6
+ /**
7
+ * Node-specific `defineCommand`. Supports three forms:
8
+ *
9
+ * ```ts
10
+ * defineCommand('agent-browser');
11
+ * defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } });
12
+ * defineCommand('gh', async (args) => ({ stdout: '...' }));
13
+ * ```
14
+ *
15
+ * Forms A and B shell out via `child_process.execFile`. Form C lets the user
16
+ * implement the command however they like. All three forms benefit from
17
+ * return-shape normalization and throw-catching — no `try`/`catch` or
18
+ * `return { stdout, stderr, exitCode: 0 }` boilerplate required.
19
+ */
20
+ const execFileAsync = promisify(execFile);
21
+ /**
22
+ * Essential, non-sensitive environment variables automatically forwarded to
23
+ * pass-through commands (forms A and B). Users can override any of these —
24
+ * or add their own (e.g. `GH_TOKEN`) — via `options.env`. Anything not listed
25
+ * here (API keys, tokens, secrets, etc.) stays on the host and is NEVER
26
+ * exposed to the spawned process unless the caller opts in explicitly.
27
+ *
28
+ * If you need full control over the env, use the function form:
29
+ * `defineCommand('gh', async (args) => { ... })`.
30
+ */
31
+ const DEFAULT_ENV = {
32
+ PATH: process.env.PATH,
33
+ HOME: process.env.HOME,
34
+ USER: process.env.USER,
35
+ LOGNAME: process.env.LOGNAME,
36
+ HOSTNAME: process.env.HOSTNAME,
37
+ SHELL: process.env.SHELL,
38
+ LANG: process.env.LANG,
39
+ LC_ALL: process.env.LC_ALL,
40
+ LC_CTYPE: process.env.LC_CTYPE,
41
+ TZ: process.env.TZ,
42
+ TERM: process.env.TERM,
43
+ TMPDIR: process.env.TMPDIR,
44
+ TMP: process.env.TMP,
45
+ TEMP: process.env.TEMP
46
+ };
47
+ function defineCommand(name, arg) {
48
+ if (typeof arg === "function") return {
49
+ name,
50
+ execute: normalizeExecutor(arg)
51
+ };
52
+ const userOpts = arg ?? {};
53
+ const mergedOpts = {
54
+ maxBuffer: 50 * 1024 * 1024,
55
+ ...userOpts,
56
+ env: {
57
+ ...DEFAULT_ENV,
58
+ ...userOpts.env ?? {}
59
+ }
60
+ };
61
+ const executor = async (args) => {
62
+ const { stdout, stderr } = await execFileAsync(name, args, mergedOpts);
63
+ return {
64
+ stdout: String(stdout ?? ""),
65
+ stderr: String(stderr ?? "")
66
+ };
67
+ };
68
+ return {
69
+ name,
70
+ execute: normalizeExecutor(executor)
71
+ };
72
+ }
73
+
74
+ //#endregion
75
+ export { defineCommand };
@@ -1,4 +1,4 @@
1
- import { b as SessionEnv, c as CommandDef, r as BashLike, u as FileStat, v as SandboxFactory, w as ShellResult } from "./types-C8tsaK1j.mjs";
1
+ import { b as SessionEnv, c as CommandDef, r as BashLike, u as FileStat, v as SandboxFactory, w as ShellResult } from "./types-BZPltYah.mjs";
2
2
 
3
3
  //#region src/sandbox.d.ts
4
4
  declare function bashToSessionEnv(bash: BashLike): SessionEnv;
package/dist/sandbox.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./agent-BYG0nVbQ.mjs";
2
- import { r as normalizePath } from "./session-BRLCNVG1.mjs";
2
+ import { r as normalizePath } from "./session-CiAMTsLZ.mjs";
3
3
 
4
4
  //#region src/sandbox.ts
5
5
  function bashToSessionEnv(bash) {
@@ -508,11 +508,13 @@ var Session = class Session {
508
508
  compactionAbortController;
509
509
  eventCallback;
510
510
  builtinTools;
511
- constructor(id, config, env, store, existingData, onAgentEvent) {
511
+ sessionCommands;
512
+ constructor(id, config, env, store, existingData, onAgentEvent, sessionCommands) {
512
513
  this.id = id;
513
514
  this.config = config;
514
515
  this.env = env;
515
516
  this.store = store;
517
+ this.sessionCommands = sessionCommands ?? [];
516
518
  this.metadata = existingData?.metadata ?? {};
517
519
  this.createdAt = existingData?.createdAt;
518
520
  this.lastCompaction = existingData?.lastCompaction;
@@ -586,8 +588,9 @@ var Session = class Session {
586
588
  const promptWithRole = this.injectRoleInstructions(text, options?.role);
587
589
  const schema = options?.result;
588
590
  const fullPrompt = buildPromptText(promptWithRole, schema);
589
- if (options?.commands) this.assertCommandSupport(options.commands);
590
- const registeredCommandNames = options?.commands ? this.registerCommands(options.commands) : [];
591
+ const effectiveCommands = this.mergeCommands(options?.commands);
592
+ if (effectiveCommands.length > 0) this.assertCommandSupport(effectiveCommands);
593
+ const registeredCommandNames = this.registerCommands(effectiveCommands);
591
594
  const registeredToolNames = options?.tools ? this.registerCustomTools(options.tools) : [];
592
595
  try {
593
596
  await this.agent.prompt(fullPrompt);
@@ -616,8 +619,9 @@ var Session = class Session {
616
619
  const schema = options?.result;
617
620
  const skillPrompt = buildSkillPrompt(registeredSkill.instructions, options?.args, schema);
618
621
  const promptWithRole = this.injectRoleInstructions(skillPrompt, options?.role);
619
- if (options?.commands) this.assertCommandSupport(options.commands);
620
- const registeredCommandNames = options?.commands ? this.registerCommands(options.commands) : [];
622
+ const effectiveCommands = this.mergeCommands(options?.commands);
623
+ if (effectiveCommands.length > 0) this.assertCommandSupport(effectiveCommands);
624
+ const registeredCommandNames = this.registerCommands(effectiveCommands);
621
625
  const registeredToolNames = options?.tools ? this.registerCustomTools(options.tools) : [];
622
626
  try {
623
627
  await this.agent.prompt(promptWithRole);
@@ -632,8 +636,9 @@ var Session = class Session {
632
636
  }
633
637
  }
634
638
  async shell(command, options) {
635
- if (options?.commands) this.assertCommandSupport(options.commands);
636
- const registeredNames = options?.commands ? this.registerCommands(options.commands) : [];
639
+ const effectiveCommands = this.mergeCommands(options?.commands);
640
+ if (effectiveCommands.length > 0) this.assertCommandSupport(effectiveCommands);
641
+ const registeredNames = this.registerCommands(effectiveCommands);
637
642
  try {
638
643
  const result = await this.env.exec(command, {
639
644
  env: options?.env,
@@ -750,6 +755,19 @@ var Session = class Session {
750
755
  if (commands.length === 0) return;
751
756
  if (!this.env.commandSupport) throw new Error("[flue] Cannot use commands: this environment does not support command registration. Commands are only available in isolate sandbox mode. Remote sandboxes handle command execution at the platform level.");
752
757
  }
758
+ /**
759
+ * Merge session-wide `commands` (from init()) with per-call commands. When
760
+ * both define a command with the same name, the per-call entry wins for
761
+ * that call.
762
+ */
763
+ mergeCommands(perCall) {
764
+ if (!perCall || perCall.length === 0) return this.sessionCommands;
765
+ if (this.sessionCommands.length === 0) return perCall;
766
+ const byName = /* @__PURE__ */ new Map();
767
+ for (const cmd of this.sessionCommands) byName.set(cmd.name, cmd);
768
+ for (const cmd of perCall) byName.set(cmd.name, cmd);
769
+ return Array.from(byName.values());
770
+ }
753
771
  registerCommands(commands) {
754
772
  if (!this.env.commandSupport || commands.length === 0) return [];
755
773
  const names = [];
@@ -146,6 +146,13 @@ interface SessionInit {
146
146
  * Precedence (highest wins): per-call `model` > role `model` > session `model` > build-time default.
147
147
  */
148
148
  model?: string;
149
+ /**
150
+ * Session-wide commands. Every prompt(), skill(), and shell() call inherits
151
+ * this list. Per-call `commands` are merged on top — if a per-call command
152
+ * shares a name with a session command, the per-call version wins for that
153
+ * call.
154
+ */
155
+ commands?: Command[];
149
156
  }
150
157
  interface FlueSession {
151
158
  prompt<S extends v.GenericSchema>(text: string, options: PromptOptions<S> & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flue/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {
@@ -23,6 +23,10 @@
23
23
  "./cloudflare": {
24
24
  "types": "./dist/cloudflare/index.d.mts",
25
25
  "import": "./dist/cloudflare/index.mjs"
26
+ },
27
+ "./node": {
28
+ "types": "./dist/node/index.d.mts",
29
+ "import": "./dist/node/index.mjs"
26
30
  }
27
31
  },
28
32
  "main": "./dist/index.mjs",