TUI CONFIG

This commit is contained in:
Raven Scott
2026-04-22 02:51:45 -04:00
parent bc737094ca
commit 6ee7cfcb57
16 changed files with 690 additions and 170 deletions
+220 -52
View File
@@ -1090,7 +1090,9 @@ function bareAgentDefaultConfig() {
request_timeout_ms: 120000,
extra_headers: /** @type {Record<string, string>} */ ({}),
allow_delete: false,
require_confirm_token: ''
require_confirm_token: '',
owner_name: '',
agent_label: ''
}
}
@@ -1122,7 +1124,9 @@ function bareAgentMergeConfig(defaults, src) {
'request_timeout_ms',
'extra_headers',
'allow_delete',
'require_confirm_token'
'require_confirm_token',
'owner_name',
'agent_label'
])
for (const k of Object.keys(src)) {
if (k.startsWith('x-')) continue
@@ -1136,7 +1140,9 @@ function bareAgentMergeConfig(defaults, src) {
k === 'rest_base_url' ||
k === 'rest_api_key' ||
k === 'model' ||
k === 'provider'
k === 'provider' ||
k === 'owner_name' ||
k === 'agent_label'
) {
out[k] = String(val ?? '')
continue
@@ -1184,7 +1190,9 @@ function bareAgentValidateConfigShape(raw) {
'request_timeout_ms',
'extra_headers',
'allow_delete',
'require_confirm_token'
'require_confirm_token',
'owner_name',
'agent_label'
]
if (!known.includes(k)) {
throw new Error('unknown config key: ' + k)
@@ -2903,7 +2911,7 @@ function bareAgentToolDefinitions() {
patch: {
type: 'object',
description:
'Partial config object (rest_base_url, model, temperature, …)'
'Partial config object (rest_base_url, model, temperature, owner_name, agent_label, …)'
}
},
required: ['patch']
@@ -4034,7 +4042,9 @@ function bareAgentMergeConfigPatch(base, patch) {
'tool_parallelism',
'request_timeout_ms',
'allow_delete',
'require_confirm_token'
'require_confirm_token',
'owner_name',
'agent_label'
]
const numKeys = new Set([
'max_tokens',
@@ -4131,6 +4141,91 @@ function bareAgentWriteOut(out, s) {
}
}
/**
* True when we can run the plain-text setup wizard on stdin/stdout (no REPL readline).
* @param {Record<string, unknown>} ctx
*/
function bareAgentCanPlainSetup(ctx) {
const stdin = /** @type {import('stream').Readable | undefined} */ (
ctx.replStdin || ctx.stdin
)
const stdout =
/** @type {import('stream').Writable | undefined} */ (
ctx.replStdout || ctx.stdout
)
const tty = /** @type {{ isTTY?: boolean }} */ (stdin)
return Boolean(
stdin &&
typeof stdin.on === 'function' &&
tty.isTTY &&
stdout &&
typeof stdout.write === 'function'
)
}
/**
* Read one line from a Readable stream (kernel echoes typed chars on cooked TTY).
* @param {import('stream').Readable} stdin
* @returns {Promise<string>}
*/
function bareAgentReadStreamLineOnce(stdin) {
return new Promise((resolve) => {
let acc = ''
/** @param {string | Uint8Array | Buffer} chunk */
function onData(chunk) {
let s = ''
if (typeof chunk === 'string') s = chunk
else if (chunk instanceof Uint8Array) s = new TextDecoder().decode(chunk)
else if (
typeof Buffer !== 'undefined' &&
typeof Buffer.isBuffer === 'function' &&
Buffer.isBuffer(chunk)
)
s = chunk.toString('utf8')
else s = String(chunk)
acc += s
const n = acc.indexOf('\n')
if (n >= 0) {
cleanup()
resolve(acc.slice(0, n).replace(/\r$/, ''))
}
}
function onEnd() {
cleanup()
resolve(acc.replace(/\r$/, ''))
}
function cleanup() {
stdin.removeListener('data', onData)
stdin.removeListener('end', onEnd)
stdin.removeListener('error', onEnd)
}
stdin.on('data', onData)
stdin.once('end', onEnd)
stdin.once('error', onEnd)
if (typeof stdin.resume === 'function') stdin.resume()
})
}
/**
* Write a prompt and read one line using raw streams (not ctx.readLine / TUI stack).
* @param {Record<string, unknown>} ctx
* @param {string} prompt
*/
async function bareAgentPromptSetupLine(ctx, prompt) {
const stdin = /** @type {import('stream').Readable | undefined} */ (
ctx.replStdin || ctx.stdin
)
const stdout =
/** @type {import('stream').Writable | undefined} */ (
ctx.replStdout || ctx.stdout
)
if (!stdin || typeof stdin.on !== 'function') {
throw new Error('setup: stdin stream unavailable')
}
bareAgentWriteOut(stdout, '\x1b[?25h\x1b[0m' + prompt)
return bareAgentReadStreamLineOnce(stdin)
}
const BARE_AGENT_STATIC_SYSTEM = `You are the Bare OS autonomous agent inside a JavaScript POSIX-like environment on Hyperdrive + Hyperswarm (Pear/Bare runtime).
JavaScript execution on this OS: **Node.js is not installed.** The \`node\`, \`npm\`, and \`npx\` commands **do not exist** and must never appear in plans or in run_command. To run JS as part of your agent work, **you must call the run_js_script tool** (writes under ~/.agent and executes via the Bare kernel). Optional: once a script exists on disk, run_command may invoke it by **absolute path** (e.g. \`/home/.../script.mjs\`)—same mechanism as \`/bin\` scripts—not via \`node\`.
@@ -4251,36 +4346,73 @@ function bareAgentFinalizeToolCalls(acc) {
* @param {boolean} opts.interactiveSetup
*/
async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) {
void opts
bareAgentLog(ctx, argv0 + ': configuring ~/.agent/config.json')
const readLine =
typeof ctx.readLine === 'function'
? /** @type {(p: string) => Promise<string | null>} */ (
ctx.readLine.bind(ctx)
)
: null
if (!readLine) {
if (!bareAgentCanPlainSetup(ctx)) {
bareAgentErr(
ctx,
argv0 +
': interactive setup requires ctx.readLine (TTY session recommended). Edit ' +
': interactive setup needs a TTY with stdin/stdout. Edit ' +
paths.config +
' manually.'
' manually or run from an interactive shell.'
)
return config
}
const url =
(await readLine(`REST base URL [${config.rest_base_url}]: `)) || ''
if (url.trim()) config.rest_base_url = url.trim()
const keyRaw =
(await readLine(`REST API key (paste; may echo) [leave empty to skip]: `)) ||
''
if (keyRaw.trim()) config.rest_api_key = keyRaw.trim()
const modelRaw =
(await readLine(`Model [${config.model}]: `)) || ''
if (modelRaw.trim()) config.model = modelRaw.trim()
const provRaw =
(await readLine(`Provider label [${config.provider}]: `)) || ''
if (provRaw.trim()) config.provider = provRaw.trim()
const stdout = ctx.replStdout || ctx.stdout
bareAgentWriteOut(
stdout,
'\n=== ' +
argv0 +
' configuration ===\n' +
'Answer each question (Enter keeps the [default]). Typed input is echoed by the terminal.\n\n'
)
try {
const owner =
(await bareAgentPromptSetupLine(
ctx,
'Owner / human name (who operates this agent) [' +
(String(config.owner_name || '').trim() || 'unset') +
']: '
)) || ''
if (owner.trim()) config.owner_name = owner.trim()
const label =
(await bareAgentPromptSetupLine(
ctx,
'Agent display name / label (shown in notes and logs) [' +
(String(config.agent_label || '').trim() || 'BareAgent') +
']: '
)) || ''
if (label.trim()) config.agent_label = label.trim()
const url =
(await bareAgentPromptSetupLine(
ctx,
'REST base URL [' + String(config.rest_base_url || '') + ']: '
)) || ''
if (url.trim()) config.rest_base_url = url.trim()
const keyRaw =
(await bareAgentPromptSetupLine(
ctx,
'REST API key (paste; may echo) [leave empty to skip]: '
)) || ''
if (keyRaw.trim()) config.rest_api_key = keyRaw.trim()
const modelRaw =
(await bareAgentPromptSetupLine(
ctx,
'Model [' + String(config.model || '') + ']: '
)) || ''
if (modelRaw.trim()) config.model = modelRaw.trim()
const provRaw =
(await bareAgentPromptSetupLine(
ctx,
'Provider label [' + String(config.provider || '') + ']: '
)) || ''
if (provRaw.trim()) config.provider = provRaw.trim()
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
bareAgentErr(ctx, argv0 + ': setup input failed: ' + msg)
return config
}
await bareAgentSaveConfig(ctx, paths, config)
bareAgentLog(ctx, 'Configuration saved.')
return config
@@ -4295,10 +4427,21 @@ async function bareAgentRunSetupOnly(ctx, argv0) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
let { config } = await bareAgentLoadOrCreateConfig(ctx, paths)
config = await bareAgentInteractiveSetup(ctx, argv0, paths, config, {
setupFlag: true,
interactiveSetup: true
})
let suspended = false
try {
if (typeof ctx.suspendReplForSubprocess === 'function') {
ctx.suspendReplForSubprocess()
suspended = true
}
config = await bareAgentInteractiveSetup(ctx, argv0, paths, config, {
setupFlag: true,
interactiveSetup: true
})
} finally {
if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') {
ctx.resumeReplAfterSubprocess()
}
}
try {
if (ctx.vfs?.chmod) await ctx.vfs.chmod(paths.config, 0o600)
} catch {
@@ -4319,20 +4462,29 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const paths = bareAgentPaths(home)
let { config } = await bareAgentLoadOrCreateConfig(ctx, paths)
const readLine =
typeof ctx.readLine === 'function'
? /** @type {(p: string) => Promise<string | null>} */ (
ctx.readLine.bind(ctx)
)
: null
const stdin = /** @type {{ isTTY?: boolean } | undefined} */ (ctx.replStdin)
const isTTY = Boolean(stdin && stdin.isTTY)
if (
const canWizard = bareAgentCanPlainSetup(ctx)
if (setupFlag && !canWizard) {
bareAgentErr(
ctx,
argv0 +
': --setup / --config needs an interactive TTY. Edit ' +
paths.config +
' manually.'
)
ctx.exitCode = 1
return
}
const needWizard =
setupFlag ||
(!(config.rest_api_key && String(config.rest_api_key).trim()) &&
readLine &&
isTTY)
) {
(!(config.rest_api_key && String(config.rest_api_key).trim()) && canWizard)
let replSuspendedForSetup = false
if (needWizard && typeof ctx.suspendReplForSubprocess === 'function') {
ctx.suspendReplForSubprocess()
replSuspendedForSetup = true
}
if (needWizard) {
config = await bareAgentInteractiveSetup(ctx, argv0, paths, config, {
setupFlag,
interactiveSetup: true
@@ -4340,6 +4492,10 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
}
if (!config.rest_api_key || !String(config.rest_api_key).trim()) {
if (replSuspendedForSetup && typeof ctx.resumeReplAfterSubprocess === 'function') {
ctx.resumeReplAfterSubprocess()
replSuspendedForSetup = false
}
bareAgentErr(
ctx,
argv0 +
@@ -4347,7 +4503,9 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
paths.config +
' or run `' +
argv0 +
' --setup`.'
' --setup` / `' +
argv0 +
' --config`.'
)
ctx.exitCode = 1
return
@@ -4361,6 +4519,10 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const fetchFn = bareAgentResolveFetch(ctx)
if (!fetchFn) {
if (replSuspendedForSetup && typeof ctx.resumeReplAfterSubprocess === 'function') {
ctx.resumeReplAfterSubprocess()
replSuspendedForSetup = false
}
bareAgentErr(ctx, argv0 + ': no fetch (ctx.httpFetch / bare.fetch / global fetch)')
ctx.exitCode = 1
return
@@ -4438,9 +4600,9 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
'/chat/completions'
const tools = bareAgentToolDefinitions()
let suspended = false
let suspended = replSuspendedForSetup
try {
if (typeof ctx.suspendReplForSubprocess === 'function') {
if (typeof ctx.suspendReplForSubprocess === 'function' && !suspended) {
ctx.suspendReplForSubprocess()
suspended = true
}
@@ -4666,12 +4828,15 @@ async function run(ctx, argv) {
ctx.console.log(
'usage: ' +
argv0 +
' [--setup | --reset] YOUR_REQUEST_HERE\n' +
' [--setup | --config | --reset] YOUR_REQUEST_HERE\n' +
' ' +
argv0 +
' --setup\n' +
' ' +
argv0 +
' --config\n' +
' ' +
argv0 +
' --reset\n' +
' ' +
argv0 +
@@ -4679,7 +4844,7 @@ async function run(ctx, argv) {
'\n' +
'Runs an autonomous coding/OS agent against any OpenAI-compatible HTTPS API.\n' +
'Configuration: ~/.agent/config.json on your personal drive (created on first run).\n' +
'Use --setup to interactively set API URL, key, model, and provider label.\n' +
'Use --setup or --config to interactively set owner name, agent label, API URL, key, model, and provider (plain TTY prompts).\n' +
'Use --reset or `reset` to clear ~/.agent/history.json and start a fresh chat session.\n' +
'\n' +
'Examples:\n' +
@@ -4691,6 +4856,9 @@ async function run(ctx, argv) {
' --setup\n' +
' ' +
argv0 +
' --config\n' +
' ' +
argv0 +
' --reset\n' +
'\n' +
'See man agent.'
@@ -4705,7 +4873,7 @@ async function run(ctx, argv) {
const rest = []
for (let i = 0; i < args.length; i++) {
const a = args[i]
if (a === '--setup') setupFlag = true
if (a === '--setup' || a === '--config') setupFlag = true
else if (a === '--reset') resetFlag = true
else rest.push(a)
}
@@ -4729,7 +4897,7 @@ async function run(ctx, argv) {
const task = rest.join(' ').trim()
if (!task && !setupFlag) {
ctx.console.error(argv0 + ': missing task (or use --setup)')
ctx.console.error(argv0 + ': missing task (or use --setup / --config)')
ctx.exitCode = 1
return
}