freddie 0.0.122 → 0.0.124

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freddie",
3
- "version": "0.0.122",
3
+ "version": "0.0.124",
4
4
  "type": "module",
5
5
  "description": "Open JS agent harness built on pi-mono, floosie, xstate, and anentrypoint-design",
6
6
  "bin": {
@@ -18,16 +18,14 @@
18
18
  "start": "node bin/freddie.js",
19
19
  "test": "node test.js",
20
20
  "build:browser": "vite build --config vite.browser.config.js",
21
+ "build:browser-shim": "node scripts/browser-shim-build/build.mjs",
21
22
  "link-local-design": "node scripts/link-local-design.mjs"
22
23
  },
23
24
  "dependencies": {
24
- "@anentrypoint/libsql-plugkit-client": "github:AnEntrypoint/libsql-plugkit-client",
25
25
  "@libsql/client": "^0.5.0",
26
- "@mariozechner/pi-agent-core": "^0.70.6",
27
26
  "@mariozechner/pi-ai": "^0.70.6",
28
- "@mariozechner/pi-coding-agent": "^0.70.6",
29
27
  "@mariozechner/pi-tui": "^0.70.6",
30
- "acptoapi": "^1.0.115",
28
+ "acptoapi": "^1.0.144",
31
29
  "anentrypoint-design": "latest",
32
30
  "commander": "^14.0.0",
33
31
  "express": "^5.0.0",
@@ -36,10 +34,8 @@
36
34
  "gm-plugkit": "^2.0.1535",
37
35
  "gm-skill": "latest",
38
36
  "js-yaml": "^4.1.0",
39
- "libsql-plugkit-client": "^0.0.10",
40
37
  "plugsdk": "^1.0.20",
41
- "xstate": "^5.31.0",
42
- "zod": "^4.0.0"
38
+ "xstate": "^5.31.0"
43
39
  },
44
40
  "optionalDependencies": {
45
41
  "@libsql/darwin-arm64": "0.3.19",
@@ -85,6 +81,7 @@
85
81
  "xstate"
86
82
  ],
87
83
  "devDependencies": {
84
+ "esbuild": "^0.28.0",
88
85
  "vite": "^8.0.13"
89
86
  }
90
87
  }
@@ -3,47 +3,51 @@ import { parseTextToolCalls } from './tool_call_text.js'
3
3
 
4
4
  const log = logger('acptoapi')
5
5
 
6
- // acptoapi may take minutes to answer when it serially walks dead providers
7
- // (each ~90s timeout) before a live one responds. Node's default undici
8
- // headersTimeout/bodyTimeout (~300s) would throw UND_ERR_HEADERS_TIMEOUT
9
- // mid-walk, so we raise them to 0 (disabled) on the global dispatcher and let
10
- // our AbortController be the single source of truth for the overall deadline
11
- // (default 240s, override via FREDDIE_LLM_TIMEOUT_MS). Use setGlobalDispatcher
12
- // once rather than a per-request Agent so we don't leak a dispatcher per call.
13
6
  // Browser-safe env read: this module evaluates in a plain browser context where
14
7
  // `process` is undefined (no node shim yet), so a bare process.env throws
15
8
  // "process is not defined" and aborts the whole bundle import. envVal() reads
16
9
  // live (picks up a late-installed shim) and never throws.
17
10
  const envVal = (k) => { try { return (typeof process !== 'undefined' && process.env) ? process.env[k] : undefined } catch { return undefined } }
18
11
  const ACPTOAPI_TIMEOUT_MS = Number(envVal('FREDDIE_LLM_TIMEOUT_MS')) || 240000
19
- let _dispatcherSet = false
20
- async function ensureLongTimeoutDispatcher() {
21
- if (_dispatcherSet) return
22
- _dispatcherSet = true
23
- try {
24
- const undici = await import('undici')
25
- // headersTimeout/bodyTimeout 0: tolerate acptoapi's minutes-long walk.
26
- // keepAlive*Timeout 1ms: close the socket right after the response so no
27
- // keep-alive socket lingers between calls.
28
- undici.setGlobalDispatcher(new undici.Agent({
29
- headersTimeout: 0,
30
- bodyTimeout: 0,
31
- keepAliveTimeout: 1,
32
- keepAliveMaxTimeout: 1,
33
- }))
34
- } catch { /* undici not available — rely on AbortController + defaults */ }
35
- }
36
12
 
37
13
  export function getAcptoapiUrl() {
38
- return envVal('FREDDIE_LLM_URL') || 'http://127.0.0.1:4800/v1'
14
+ // Returns the configured dialable acptoapi URL, or null when unset. Most
15
+ // callers (the dashboard health row, CLI banner) treat this as display/logging
16
+ // only -- there is no listening port required for the in-process callLLM()
17
+ // path below. However codex_responses_adapter.js, gemini_native_adapter.js,
18
+ // image_gen_provider.js, and model-discovery.js still fetch() this value as a
19
+ // live HTTP base and DO require a real dialable URL (FREDDIE_LLM_URL set) --
20
+ // they must guard against a null return rather than building a request
21
+ // against a placeholder string.
22
+ return envVal('FREDDIE_LLM_URL') || null
39
23
  }
40
24
 
41
25
  export function getAcptoapiModel() {
42
26
  return envVal('FREDDIE_LLM_MODEL') || 'claude/haiku'
43
27
  }
44
28
 
29
+ let _acptoapi = null
30
+ async function getAcptoapi() {
31
+ if (!_acptoapi) {
32
+ const mod = await import('acptoapi')
33
+ // acptoapi is a CJS package; Node's CJS-to-ESM interop only statically
34
+ // detects a SUBSET of module.exports keys as named exports (witnessed:
35
+ // `chat` is a real named export, `listAllModelsAndQueues` is not, even
36
+ // though both are plain keys on the same module.exports object) -- read
37
+ // through `.default` (the full CJS exports object) so every export is
38
+ // reachable regardless of which subset the interop happened to pick up.
39
+ _acptoapi = mod.default && typeof mod.default === 'object' ? mod.default : mod
40
+ }
41
+ return _acptoapi
42
+ }
43
+
44
+ // In-process call: acptoapi's own chat() walks its provider/model resolution
45
+ // (including a comma-list or named chain for multi-model fallback) with no
46
+ // HTTP hop and no separate listening process -- eliminates the standalone
47
+ // acptoapi.js daemon on :4800 entirely, along with its witnessed failure mode
48
+ // (an uncaught ACP-timeout exception crashing the whole bridge process).
45
49
  export async function callLLM({ messages, tools = [], model, tool_choice, cwd = null } = {}) {
46
- const base = getAcptoapiUrl()
50
+ const acptoapi = await getAcptoapi()
47
51
  const useModel = model || getAcptoapiModel()
48
52
  const hasTools = Array.isArray(tools) && tools.length > 0
49
53
  const adaptedMessages = messages.map(adaptMessage)
@@ -59,46 +63,40 @@ export async function callLLM({ messages, tools = [], model, tool_choice, cwd =
59
63
  if (sysIdx >= 0) adaptedMessages[sysIdx] = { ...adaptedMessages[sysIdx], content: (adaptedMessages[sysIdx].content || '') + cwdNote }
60
64
  else adaptedMessages.unshift({ role: 'system', content: cwdNote.trim() })
61
65
  }
62
- const body = {
63
- model: useModel,
64
- messages: adaptedMessages,
65
- stream: false,
66
- max_tokens: 4096,
67
- }
68
- if (hasTools) body.tools = tools.map(adaptTool)
69
- // Forward a forced tool_choice (e.g. 'required') so a caller can compel a tool
70
- // call on a turn. Only when tools are present; absent -> the model chooses.
71
- if (hasTools && tool_choice) body.tool_choice = tool_choice
72
- const headers = { 'content-type': 'application/json', authorization: 'Bearer none' }
73
- if (hasTools && cwd) headers['x-cwd'] = cwd
74
- // Rely on AbortController timeout. acptoapi v1+ ships CORS + Private
75
- // Network Access headers so cross-origin loopback (gh-pages → localhost)
76
- // succeeds when acptoapi is running. The earlier preemptive loopback
77
- // refusal caused false negatives on reachable endpoints.
78
- await ensureLongTimeoutDispatcher()
79
- const _ac = new AbortController()
80
- const _tid = setTimeout(() => _ac.abort(new Error('acptoapi fetch timeout')), ACPTOAPI_TIMEOUT_MS)
81
- let res
82
- try {
83
- res = await fetch(base.replace(/\/$/, '') + '/chat/completions', {
84
- method: 'POST',
85
- headers,
86
- body: JSON.stringify(body),
87
- signal: _ac.signal,
88
- })
89
- } finally { clearTimeout(_tid) }
90
- if (!res.ok) {
91
- const text = await res.text()
92
- throw new Error(`acptoapi ${res.status}: ${text.slice(0, 400)}`)
93
- }
66
+ // acptoapi's chat() is a plain Promise with no AbortSignal support (each
67
+ // underlying provider has its own internal per-call timeout, e.g. the
68
+ // openai-compat provider's OPENAI_COMPAT_TIMEOUT_MS, default 180s) -- so the
69
+ // overall deadline here is enforced by racing a timeout, not by aborting the
70
+ // in-flight call itself.
71
+ let _timeoutHandle
72
+ const _timeout = new Promise((_, reject) => {
73
+ _timeoutHandle = setTimeout(() => reject(new Error('acptoapi call timeout')), ACPTOAPI_TIMEOUT_MS)
74
+ })
94
75
  let json
95
76
  try {
96
- json = await res.json()
97
- } catch (e) {
98
- throw new Error(`acptoapi ${res.status}: invalid JSON response: ${String(e)}`)
99
- }
77
+ json = await Promise.race([
78
+ acptoapi.chat({
79
+ model: useModel,
80
+ messages: adaptedMessages,
81
+ ...(hasTools ? { tools: tools.map(adaptTool) } : {}),
82
+ max_tokens: 4096,
83
+ }),
84
+ _timeout,
85
+ ])
86
+ } finally { clearTimeout(_timeoutHandle) }
100
87
  log.info('completed', { model: useModel, usage: json.usage })
101
- return adaptResponse(json)
88
+ const adapted = adaptResponse(json)
89
+ // acptoapi's chat()/toParams() does not forward tool_choice to any provider
90
+ // (confirmed: a pre-existing gap, not introduced by going in-process -- the
91
+ // old HTTP path silently dropped it too). Enforce the documented contract
92
+ // client-side: when the caller forced a tool call and none came back, this
93
+ // was previously a SILENT no-op -- log loud so the gap is visible instead of
94
+ // masquerading as "the model chose not to call a tool".
95
+ const forcedToolChoice = tool_choice === 'required' || tool_choice?.type === 'required'
96
+ if (forcedToolChoice && hasTools && !adapted.tool_calls.length) {
97
+ log.warn('tool_choice required but no tool call returned (acptoapi does not enforce tool_choice)', { model: useModel })
98
+ }
99
+ return adapted
102
100
  }
103
101
 
104
102
  function adaptMessage(m) {
@@ -145,21 +143,23 @@ function adaptResponse(r) {
145
143
 
146
144
  function tryParseJson(s) { try { return typeof s === 'string' ? JSON.parse(s) : (s || {}) } catch { return {} } }
147
145
 
146
+ // In-process reachability: acptoapi is a library import now, not a port to
147
+ // dial, so there is no cheap side-channel that answers "is the CONFIGURED
148
+ // model reachable" for an arbitrary provider (witnessed: listAllModelsAndQueues
149
+ // only enumerates configured matrix/queue sources, empty by default; the
150
+ // sampler cache starts empty until something actually probes/fails a
151
+ // provider; chatjimmy -- casey's configured model -- is itself a remote
152
+ // hosted proxy at https://chatjimmy.ai with its own internal model list, not
153
+ // exported from acptoapi's top-level API at all). The only generically
154
+ // correct answer is a real minimal call: send a trivial message with a short
155
+ // timeout and treat success as reachable.
148
156
  export async function isReachable(timeoutMs = 10000) {
149
157
  try {
150
- const controller = new AbortController()
151
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
152
- try {
153
- const res = await fetch(getAcptoapiUrl().replace(/\/$/, '') + '/models', {
154
- headers: { authorization: 'Bearer none' },
155
- signal: controller.signal
156
- })
157
- clearTimeout(timeoutId)
158
- if (!res.ok) return false
159
- const json = await res.json()
160
- return Array.isArray(json.data) && json.data.length > 0
161
- } finally {
162
- clearTimeout(timeoutId)
163
- }
158
+ const acptoapi = await getAcptoapi()
159
+ const result = await Promise.race([
160
+ acptoapi.chat({ model: getAcptoapiModel(), messages: [{ role: 'user', content: 'ping' }], max_tokens: 4 }),
161
+ new Promise((_, reject) => setTimeout(() => reject(new Error('reachability probe timeout')), timeoutMs)),
162
+ ])
163
+ return !!(result && result.choices && result.choices.length)
164
164
  } catch { return false }
165
165
  }
@@ -4,7 +4,9 @@ import { isCodexModel } from '../cli/codex_models.js'
4
4
 
5
5
  export async function chat({ input, model = 'o3-mini', tools = [], reasoning_effort = 'medium' } = {}) {
6
6
  if (!isCodexModel(model)) console.warn('[codex_responses] non-codex model: ' + model)
7
- const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
7
+ const url = getAcptoapiUrl()
8
+ if (!url) throw new Error('FREDDIE_LLM_URL must be set for this adapter (acptoapi is in-process only otherwise)')
9
+ const base = url.replace(/\/v1\/?$/, '')
8
10
  const r = await fetch(base + '/v1/responses', {
9
11
  method: 'POST',
10
12
  headers: { authorization: 'Bearer none', 'content-type': 'application/json' },
@@ -3,7 +3,9 @@ import { getAcptoapiUrl } from './acptoapi-bridge.js'
3
3
  import { adaptToolForGemini, adaptMessagesForGemini } from './gemini_schema.js'
4
4
 
5
5
  export async function chat({ messages, model = 'gemini-2.5-flash', tools = [] } = {}) {
6
- const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
6
+ const acptoapiUrl = getAcptoapiUrl()
7
+ if (!acptoapiUrl) throw new Error('FREDDIE_LLM_URL must be set for this adapter (acptoapi is in-process only otherwise)')
8
+ const base = acptoapiUrl.replace(/\/v1\/?$/, '')
7
9
  const url = `${base}/v1beta/models/${model}:generateContent`
8
10
  const body = { contents: adaptMessagesForGemini(messages), ...(tools.length ? { tools: [{ function_declarations: tools.map(adaptToolForGemini) }] } : {}) }
9
11
  const r = await fetch(url, {
@@ -5,7 +5,9 @@ const PROVIDERS = ['openai', 'replicate', 'stability']
5
5
 
6
6
  export async function generate({ provider = 'openai', prompt, size, model } = {}) {
7
7
  if (!PROVIDERS.includes(provider)) throw new Error('unknown image provider: ' + provider)
8
- const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
8
+ const url = getAcptoapiUrl()
9
+ if (!url) throw new Error('FREDDIE_LLM_URL must be set for this adapter (acptoapi is in-process only otherwise)')
10
+ const base = url.replace(/\/v1\/?$/, '')
9
11
  const body = provider === 'replicate'
10
12
  ? { version: model || 'black-forest-labs/flux-schnell', input: { prompt } }
11
13
  : { model: model || 'gpt-image-1', prompt, size }
@@ -5,6 +5,22 @@ import { parseTextToolCalls } from './tool_call_text.js'
5
5
  import * as sdkNs from 'acptoapi'
6
6
  export { matrixUsable } from './model-matrix.js'
7
7
 
8
+ // Reachability memoization: bridgeReachable() now performs a REAL LLM call
9
+ // (acptoapi-bridge.js's isReachable() sends a live 'ping' completion), so
10
+ // calling it on every turn doubles LLM cost/latency. Cache the result for a
11
+ // short TTL so a burst of turns within the window reuses one probe. Does NOT
12
+ // touch acptoapi-bridge.js's exported isReachable -- health-check/dashboard
13
+ // callers still need a live, uncached probe.
14
+ let _lastReachable = { at: 0, ok: false }
15
+ const REACHABLE_TTL_MS = 5000
16
+ async function cachedReachable() {
17
+ const now = Date.now()
18
+ if (now - _lastReachable.at < REACHABLE_TTL_MS) return _lastReachable.ok
19
+ const ok = await bridgeReachable()
20
+ _lastReachable = { at: now, ok }
21
+ return ok
22
+ }
23
+
8
24
  // `acptoapi` is externalized by vite (browser) so the host environment
9
25
  // supplies it (thebird ships docs/lib/acptoapi-browser.js via importmap).
10
26
  // Node CLI gets the real CJS package. Defensive `|| {}` keeps the bundle
@@ -78,7 +94,7 @@ async function buildModel({ provider, model, inputModel }) {
78
94
  const keyed = Array.isArray(auto) ? auto.filter(l => { const p = l.model.split('/')[0]; const env = PROVIDER_KEYS[p]; return env && process.env[env] }) : []
79
95
  if (keyed.length) return keyed.map(l => l.model).join(', ')
80
96
  // No local provider keys — delegate to acptoapi if reachable.
81
- if (await bridgeReachable()) return process.env.FREDDIE_LLM_MODEL || 'auto'
97
+ if (await cachedReachable()) return process.env.FREDDIE_LLM_MODEL || 'auto'
82
98
  return null
83
99
  }
84
100
 
@@ -87,12 +103,12 @@ export function resolveCallLLM({ provider, model } = {}) {
87
103
  const m = await buildModel({ provider, model, inputModel: input.model })
88
104
  if (!m) {
89
105
  const status = typeof sdk.getStatus === 'function' ? sdk.getStatus().map(s => `${s.provider}(ok=${s.ok},fails=${s.failCount})`).join(', ') : ''
90
- throw new Error('no LLM backend reachable: set a provider API key or start acptoapi (http://127.0.0.1:4800/v1)' + (status ? ' | sampler: ' + status : ''))
106
+ throw new Error('no LLM backend reachable: set a provider API key or FREDDIE_LLM_MODEL' + (status ? ' | sampler: ' + status : ''))
91
107
  }
92
108
  try {
93
109
  const isSimple = typeof m === 'string' && !m.includes(',') && !/^queue\//.test(m)
94
110
 
95
- if (isSimple && await bridgeReachable()) {
111
+ if (isSimple && await cachedReachable()) {
96
112
  return await bridgeCall({ ...input, model: m })
97
113
  }
98
114
 
@@ -105,7 +121,9 @@ export function resolveCallLLM({ provider, model } = {}) {
105
121
  if (m.includes(',') || /^queue\//.test(m)) opts.matrixSource = process.env.FREDDIE_MATRIX_URL || MATRIX_FILE
106
122
 
107
123
  if (typeof sdk.chat !== 'function') {
108
- // Browser context: no node-side sdk; route via HTTP bridge.
124
+ // Browser/no-sdk context: fall back to acptoapi-bridge's in-process
125
+ // call (may be a no-op/broken in true browser bundles since acptoapi
126
+ // is externalized for vite -- unverified post-rewrite, see build:browser).
109
127
  return await bridgeCall({ ...input, model: m })
110
128
  }
111
129
  const r = await sdk.chat(opts)
@@ -17,7 +17,9 @@ export function listKnownProviders() {
17
17
  }
18
18
 
19
19
  export async function discoverModels({ provider } = {}) {
20
- const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
20
+ const url = getAcptoapiUrl()
21
+ if (!url) throw new Error('FREDDIE_LLM_URL must be set for this adapter (acptoapi is in-process only otherwise)')
22
+ const base = url.replace(/\/v1\/?$/, '')
21
23
  try {
22
24
  const r = await fetch(base + '/v1/models', {
23
25
  headers: { authorization: 'Bearer none' },
package/src/host/index.js CHANGED
@@ -5,7 +5,7 @@ import { getFreddieHome } from '../home.js'
5
5
  import { applyActiveProjectFromRegistry } from '../projects.js'
6
6
 
7
7
  let _host = null
8
- let _loaded = false
8
+ let _loadPromise = null
9
9
 
10
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
11
11
  const REPO_PLUGINS = path.resolve(__dirname, '..', '..', 'plugins')
@@ -16,18 +16,24 @@ export function host() {
16
16
  }
17
17
 
18
18
  export async function bootHost(extraRoots = []) {
19
- const h = host()
20
- if (_loaded) return h
21
- _loaded = true
22
- if (!process.env.FREDDIE_HOME && !process.env.FREDDIE_PROFILE) applyActiveProjectFromRegistry()
23
- const roots = [REPO_PLUGINS, path.join(getFreddieHome(), 'plugins'), path.join(process.cwd(), '.freddie', 'plugins'), ...extraRoots]
24
- const plugins = await discoverPlugins(roots)
25
- await h.load(plugins)
26
- const ccRoots = [path.join(getFreddieHome(), 'cc-plugins'), path.join(process.cwd(), '.freddie', 'cc-plugins')]
27
- await h.loadCcPlugins(ccRoots)
28
- const extra = (process.env.FREDDIE_EXTRA_CC_ROOTS || '').split(path.delimiter).filter(Boolean)
29
- for (const r of [__dirname, process.cwd(), ...extra]) await h.loadCcFromNodeModules(r)
30
- return h
19
+ // Memoize the IN-FLIGHT promise, not a boolean flag: a boolean set true
20
+ // before the awaits below complete let concurrent callers observe a
21
+ // partially-loaded host. Returning the same promise means every caller
22
+ // (including ones that arrive mid-load) awaits the exact same completion.
23
+ if (_loadPromise) return _loadPromise
24
+ _loadPromise = (async () => {
25
+ const h = host()
26
+ if (!process.env.FREDDIE_HOME && !process.env.FREDDIE_PROFILE) applyActiveProjectFromRegistry()
27
+ const roots = [REPO_PLUGINS, path.join(getFreddieHome(), 'plugins'), path.join(process.cwd(), '.freddie', 'plugins'), ...extraRoots]
28
+ const plugins = await discoverPlugins(roots)
29
+ await h.load(plugins)
30
+ const ccRoots = [path.join(getFreddieHome(), 'cc-plugins'), path.join(process.cwd(), '.freddie', 'cc-plugins')]
31
+ await h.loadCcPlugins(ccRoots)
32
+ const extra = (process.env.FREDDIE_EXTRA_CC_ROOTS || '').split(path.delimiter).filter(Boolean)
33
+ for (const r of [__dirname, process.cwd(), ...extra]) await h.loadCcFromNodeModules(r)
34
+ return h
35
+ })()
36
+ return _loadPromise
31
37
  }
32
38
 
33
- export function resetHostForTests() { _host = null; _loaded = false }
39
+ export function resetHostForTests() { _host = null; _loadPromise = null }
@@ -1,24 +0,0 @@
1
-
2
- import path from 'path';
3
- const F='C:/dev/freddie';
4
- const R=p=>path.resolve(p);
5
- const ALIAS={
6
- [R(F+'/src/host/index.js')]: R(F+'/src/agent/__browser_shims/host.js'),
7
- [R(F+'/src/toolsets.js')]: R(F+'/src/agent/__browser_shims/toolsets.js'),
8
- [R(F+'/src/agent/llm_resolver.js')]: R(F+'/src/agent/__browser_shims/llm_resolver.js'),
9
- [R(F+'/src/observability/log.js')]: R(F+'/src/agent/__browser_shims/log.js'),
10
- [R(F+'/src/config.js')]: R(F+'/src/agent/__browser_shims/config.js'),
11
- [R(F+'/src/home.js')]: R(F+'/src/agent/__browser_shims/home.js'),
12
- };
13
- export default {
14
- name: 'freddie-browser-alias',
15
- setup(build){
16
- build.onResolve({filter: /.*/}, async args=>{
17
- if(args.path.startsWith('node:') || (!args.path.startsWith('.') && !path.isAbsolute(args.path))) return null;
18
- const resolved = path.resolve(args.resolveDir||'', args.path);
19
- const candidates=[resolved, resolved+'.js', resolved+'/index.js'];
20
- for(const c of candidates) if(ALIAS[c]) return { path: ALIAS[c] };
21
- return null;
22
- });
23
- }
24
- };
@@ -1,18 +0,0 @@
1
-
2
- import * as esbuild from 'esbuild';
3
- import alias from './alias-plugin.mjs';
4
- await esbuild.build({
5
- entryPoints: ['C:/dev/freddie/src/agent/__browser_shims/entry.js'],
6
- bundle: true,
7
- format: 'esm',
8
- platform: 'browser',
9
- conditions: ['browser','module','import'],
10
- outfile: 'C:/dev/thebird/docs/freddie-runtime.js',
11
- plugins: [alias],
12
- legalComments: 'none',
13
- minify: false,
14
- sourcemap: false,
15
- logLevel: 'info',
16
- external: [],
17
- });
18
- console.log('bundle ok');