TUI CONFIG
This commit is contained in:
@@ -55,7 +55,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: ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +89,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
|
||||
@@ -101,7 +105,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
|
||||
@@ -149,7 +155,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)
|
||||
|
||||
@@ -185,7 +185,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']
|
||||
@@ -1316,7 +1316,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',
|
||||
|
||||
@@ -44,6 +44,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\`.
|
||||
@@ -164,36 +249,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
|
||||
@@ -208,10 +330,32 @@ 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
|
||||
})
|
||||
if (!bareAgentCanPlainSetup(ctx)) {
|
||||
bareAgentErr(
|
||||
ctx,
|
||||
argv0 +
|
||||
': --setup / --config needs an interactive TTY. Edit ' +
|
||||
paths.config +
|
||||
' manually.'
|
||||
)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
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 {
|
||||
@@ -232,20 +376,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
|
||||
@@ -253,6 +406,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 +
|
||||
@@ -260,7 +417,9 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
|
||||
paths.config +
|
||||
' or run `' +
|
||||
argv0 +
|
||||
' --setup`.'
|
||||
' --setup` / `' +
|
||||
argv0 +
|
||||
' --config`.'
|
||||
)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
@@ -274,6 +433,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
|
||||
@@ -351,9 +514,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
|
||||
}
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
"section": 1,
|
||||
"title": "OpenAI-compatible autonomous coding agent",
|
||||
"synopsis": [
|
||||
"agent [--setup | --reset]",
|
||||
"agent [--setup | --config | --reset]",
|
||||
"agent reset",
|
||||
"agent [--setup] REQUEST"
|
||||
"agent [--setup | --config] REQUEST"
|
||||
],
|
||||
"description": "Runs a persistent ReAct-style loop against any OpenAI-compatible HTTPS API (Groq, OpenAI, xAI, etc.). **No environment variables** are used for credentials: everything lives in **`~/.agent/config.json`** on your **personal Hyperdrive**, created on first run with Groq-oriented defaults. Config also supports **`allow_delete`** (default false) and optional **`require_confirm_token`** for the **`delete_path`** tool when delete is enabled.\n\nOn a **TTY**, the interactive shell line editor is suspended during the session (**`suspendReplForSubprocess`**). Assistant output streams with ANSI highlighting; tool invocations are labeled.\n\n**Interrupt:** **Ctrl+C** aborts in-flight HTTPS requests (**`SIGINT`** / **`AbortSignal`**) and restores the REPL.\n\n**HTTPS policy:** Delegated **`fetch`** follows the booter (**`ctx.httpFetch`**). Where **`BARE_OS_HTTP_ALLOWLIST`** is set, operators must include your provider hostname (for example **`api.groq.com`**, **`api.openai.com`**, **`api.x.ai`**) **and** every host the **`web_fetch`** tool may retrieve (**`http(s)`** pages or APIs for the model).\n\n**Tools (VFS & shell):** **`read_file`**, **`write_file`**, **`edit_file`**, **`create_directory`**, **`list_directory`**, **`file_stat`**, **`move_path`**, **`delete_path`**, **`search_files`** (**`grep`**), **`run_command`** (optional **`capture_exit`** for a final **`EXIT:`** line in the capture file), **`run_js_script`**, **`run_js_script_at_path`**, **`get_system_info`**, **`get_resource_limits`**, **`read_proc_file`**, **`get_swarm_peers`**, **`read_man_page`**, **`apropos_man`**, **`read_skill`** (load **`SKILL.md`** from **`~/.agent/workspace/skills/`** or **`~/.agent/skills/`**), **`edit_agent_config`**, **`list_bin`**, **`task_complete`**. **Node is not installed** — use **`run_js_script`** (or a saved **`.mjs`** with **`run_js_script_at_path`**) for JavaScript; not via **`node` / `npx`**. The merged manual is at **`/share/man/man.json`**; prefer **`read_man_page`** / **`apropos_man`** over shelling **`man`** when you only need text.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "--setup",
|
||||
"meaning": "interactive prompts for REST base URL, API key, model, provider; writes **`~/.agent/config.json`**"
|
||||
"meaning": "interactive TTY wizard (**`--config`** alias): owner name, agent label, REST base URL, API key, model, provider; writes **`~/.agent/config.json`** (plain stdin/stdout; suspends shell line editor first)"
|
||||
},
|
||||
{
|
||||
"flag": "--config",
|
||||
"meaning": "same as **`--setup`**"
|
||||
},
|
||||
{
|
||||
"flag": "--help",
|
||||
@@ -57,5 +61,5 @@
|
||||
{ "name": "wget", "section": 1 },
|
||||
{ "name": "chat", "section": 1 }
|
||||
],
|
||||
"bareOsNotes": "Session blocks shell input via REPL suspend; **`ctx.readLine`** masking for API keys depends on booter capabilities. Prefer **`chmod 600`** on **`config.json`** (best-effort)."
|
||||
"bareOsNotes": "Session blocks shell input via REPL suspend. **`--setup`** / **`--config`** use plain stdin/stdout prompts (not **`ctx.readLine`**). API key may echo in the terminal. Prefer **`chmod 600`** on **`config.json`** (best-effort)."
|
||||
}
|
||||
|
||||
@@ -9,12 +9,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 +
|
||||
@@ -22,7 +25,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' +
|
||||
@@ -34,6 +37,9 @@ async function run(ctx, argv) {
|
||||
' --setup\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' --config\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' --reset\n' +
|
||||
'\n' +
|
||||
'See man agent.'
|
||||
@@ -48,7 +54,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)
|
||||
}
|
||||
@@ -72,7 +78,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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema": 2,
|
||||
"profileId": "bare-os-posix-like",
|
||||
"generatedAt": "2026-04-22T06:45:22.874Z",
|
||||
"generatedAt": "2026-04-22T06:51:11.664Z",
|
||||
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
|
||||
"commandIndex": [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"atMs": 1776840322873,
|
||||
"atMs": 1776840671663,
|
||||
"commands": [
|
||||
"agent",
|
||||
"arch",
|
||||
|
||||
@@ -18,14 +18,14 @@ Use this skill whenever the user asks for system status, peer count, drive healt
|
||||
2. Call **`get_swarm_peers`** (and **`get_system_info`** as needed) for swarm / session context when exposed by the booter.
|
||||
3. Use **`read_proc_file`** on **`/proc/bare_os/*`** mirrors when the user cares about kernel/session metrics (see the **bare-os-kernel-proc** skill for feature/capability JSON).
|
||||
4. Check SSH/kernel narrative only when **`read_proc_file`** or **`read_man_page`** confirms how this image exposes **`sshd`** (do not assume **`node`** exists).
|
||||
4. Summarize in clear bullet points:
|
||||
5. Summarize in clear bullet points:
|
||||
|
||||
- Drive sync status
|
||||
- Active peers
|
||||
- Any warnings or errors
|
||||
- Recent MEMORY.md highlights
|
||||
|
||||
## Output Format
|
||||
## Output format
|
||||
|
||||
Always respond with:
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user