@causyne/agent 1.10.0 → 1.10.5

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/LICENSE CHANGED
@@ -9,7 +9,7 @@ Parameters
9
9
 
10
10
  Licensor: Ikbal Fadilah
11
11
 
12
- Licensed Work: Causyne Agent 1.10.0
12
+ Licensed Work: Causyne Agent 1.10.1
13
13
  The Licensed Work is (c) 2026 Ikbal Fadilah.
14
14
 
15
15
  Additional Use Grant: You may make use of the Licensed Work, provided that
package/README.md CHANGED
@@ -12,6 +12,12 @@ documentation is intentionally concise while the project is evolving.
12
12
 
13
13
  Learn more at [causyne.com](https://causyne.com/).
14
14
 
15
+ ## Contact
16
+
17
+ - General inquiries and business communication: [contact@causyne.com](mailto:contact@causyne.com)
18
+ - Product support and troubleshooting: [help@causyne.com](mailto:help@causyne.com)
19
+ - Partnerships and direct founder communication: [ikbal@causyne.com](mailto:ikbal@causyne.com)
20
+
15
21
  ## Development
16
22
 
17
23
  Node.js 24 or newer is required.
@@ -2,8 +2,8 @@
2
2
 
3
3
  Causyne Agent is the renamed continuation of **Vanexa Agent**. It is not
4
4
  presented as an unrelated project or as a way to bypass
5
- security classification. Version 1.10.0 is the local source migration successor
6
- to the 1.9.20 working tree. This does not assert a published release.
5
+ security classification. Version 1.10.0 was the first published local-source
6
+ migration successor to the 1.9.20 working tree.
7
7
 
8
8
  The previous npm identities were `@ikbal_fadilah_vanexa01/vanexa-agent` and
9
9
  `@vanexalabs-ai/vanexa-agent`. The dispute at
@@ -9,23 +9,17 @@ license identifiers:
9
9
 
10
10
  | License expression | Lockfile package entries |
11
11
  | --- | ---: |
12
- | MIT | 87 |
13
- | Apache-2.0 | 22 |
14
- | BSD-2-Clause | 8 |
15
- | ISC | 5 |
16
- | BSD-3-Clause | 1 |
17
- | BlueOak-1.0.0 | 1 |
18
- | 0BSD | 1 |
19
- | MIT OR CC0-1.0 | 1 |
20
-
21
- The lockfile contained 126 dependency entries in total. npm's audit metadata
22
- reported 106 production dependencies; platform-specific optional entries and
23
- development dependencies account for the differing views of the graph.
24
-
25
- The direct runtime dependencies are `@babel/parser`, `acorn-walk`, `commander`,
26
- `duck-duck-scrape`, `googlethis`, and `inquirer`. Direct
27
- development dependencies are `@types/node`, `acorn`, and `typescript`. Exact
28
- versions and integrity hashes are recorded in `package-lock.json`.
12
+ | Apache-2.0 | 21 |
13
+ | MIT | 9 |
14
+
15
+ The lockfile contained 30 dependency entries in total. Three are production
16
+ entries; development and platform-specific optional entries account for the
17
+ rest. npm's audit metadata reported 12 installed packages.
18
+
19
+ The direct runtime dependencies are `acorn-walk` and `commander`. Direct
20
+ development dependencies are `@babel/parser`, `@types/node`, `acorn`, and
21
+ `typescript`. Exact versions and integrity hashes are recorded in
22
+ `package-lock.json`.
29
23
 
30
24
  No dependency in the reviewed main lockfile was marked with an install script.
31
25
  Optional capability dependencies are installed only after approval into a
@@ -42,7 +36,7 @@ the licenses of third-party dependencies.
42
36
 
43
37
  The terms section of `LICENSE` was normalized and compared with the SPDX
44
38
  BUSL-1.1 canonical text during this review; it matched. The project-specific
45
- parameter block selects the named licensor, Causyne Agent 1.10.0 as the licensed
39
+ parameter block selects the named licensor, Causyne Agent 1.10.1 as the licensed
46
40
  work, 2030-08-09 as the change date, and Apache-2.0 as the change
47
41
  license. The Additional Use Grant permits production use except a paid
48
42
  competing commercial offering as defined in that block. These business terms
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@causyne/agent",
3
- "version": "1.10.0",
3
+ "version": "1.10.5",
4
4
  "description": "Agentic AI for software, tools, and computer workflows",
5
5
  "main": "bin/causyne.js",
6
6
  "type": "module",
@@ -49,19 +49,12 @@
49
49
  "bugs": {
50
50
  "url": "https://github.com/ikbal-causyne/causyne-agent/issues"
51
51
  },
52
- "overrides": {
53
- "form-data": "^4.0.0",
54
- "axios": "^1.20.0"
55
- },
56
52
  "dependencies": {
57
- "@babel/parser": "^8.0.4",
58
53
  "acorn-walk": "^8.3.5",
59
- "commander": "^11.1.0",
60
- "duck-duck-scrape": "^2.2.7",
61
- "googlethis": "^1.8.0",
62
- "inquirer": "^9.2.12"
54
+ "commander": "^11.1.0"
63
55
  },
64
56
  "devDependencies": {
57
+ "@babel/parser": "^8.0.7",
65
58
  "@types/node": "^22.20.2",
66
59
  "acorn": "^8.18.0",
67
60
  "typescript": "^7.0.2"
package/src/cliMode.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { AgentLoop } from './agentLoop.js';
2
2
  import { loadConfig, saveConfig } from './config.js';
3
- import inquirer from './utils/inquirer.js';
4
3
  import readline from 'readline';
5
4
  import { ChatPrompt, isInteractive } from './tui/prompt.js';
6
5
  import { handleInput, listCommands, isCommandInput } from './commands.js';
@@ -78,7 +77,7 @@ function makeApprovalHandler(conn, dialogs = null) {
78
77
  conn.stopSpinner();
79
78
 
80
79
  // An interactive session asks in a panel that shows the risk, the arguments
81
- // and a scrollable diff in one frame. The inquirer path below asks the same
80
+ // and a scrollable diff in one frame. The plain fallback asks the same
82
81
  // question in the scrollback, which is all a non-TTY session can do.
83
82
  if (dialogs && detail) {
84
83
  // Loops only for "Allow always" → Cancel: backing out of the standing
@@ -100,7 +99,11 @@ function makeApprovalHandler(conn, dialogs = null) {
100
99
  // The text says exactly what is being given up and for how long.
101
100
  const confirmed = await dialogs.confirm(
102
101
  `Allow ${bold(detail.tool)} to run WITHOUT asking, for the rest of this session?\n`
103
- + ` This lasts until Causyne restarts. Undo it any time with /approvals clear.`
102
+ + ` This lasts until Causyne restarts. Undo it any time with /approvals clear.`,
103
+ // Reaching this question already required moving onto Allow always
104
+ // and pressing Enter. A second Enter is therefore an explicit second
105
+ // confirmation, rather than a reflex approval on the first dialog.
106
+ { defaultAnswer: true }
104
107
  );
105
108
  if (confirmed) break;
106
109
  // Cancelled: ask again rather than denying something they never rejected.
@@ -139,7 +142,7 @@ function makeApprovalHandler(conn, dialogs = null) {
139
142
 
140
143
  // No live region to draw a panel into (a piped session, or raw mode
141
144
  // refused). askConfirm degrades to readline on its own, so this path keeps
142
- // the same wording as the panel instead of inquirer's.
145
+ // the same wording as the panel instead of a second prompt framework's.
143
146
  const approved = await askConfirm(message);
144
147
 
145
148
  if (conn.pendingApprovals) conn.pendingApprovals.delete(toolCallId);
@@ -322,7 +325,7 @@ export async function chatMode({ home = false } = {}) {
322
325
  // The prompt owns the keyboard so the chat can offer history, completion and
323
326
  // typing while a turn runs. It is skipped when stdin is not a terminal (piped
324
327
  // input, CI) or when CAUSYNE_SIMPLE_INPUT=1, and any failure to take raw mode
325
- // falls back to the inquirer path below — a better editor must never be the
328
+ // falls back to the plain readline path below — a better editor must never be the
326
329
  // reason chat refuses to start.
327
330
  let prompt = null;
328
331
  if (isInteractive()) {
@@ -367,9 +370,9 @@ export async function chatMode({ home = false } = {}) {
367
370
  // one that was asked for.
368
371
  if (surface) prompt.draw();
369
372
 
370
- // Ask for approval in the prompt's own panel, not inquirer's.
373
+ // Ask for approval in the prompt's own panel, not a competing reader.
371
374
  //
372
- // inquirer.prompt() takes stdin while this prompt is already holding it in
375
+ // A second prompt reader would take stdin while this prompt already holds it in
373
376
  // raw mode: the two contend, and the question can land on a half-drawn
374
377
  // prompt. The panel shows the risk, the arguments and a scrollable diff in
375
378
  // one frame, and dismissing it denies.
@@ -394,12 +397,11 @@ export async function chatMode({ home = false } = {}) {
394
397
 
395
398
  const readTask = async () => {
396
399
  if (prompt) return prompt.read();
397
- const { task } = await inquirer.prompt([{
398
- type: 'input',
399
- name: 'task',
400
- message: '\x1b[36mYou:\x1b[0m'
401
- }]);
402
- return task;
400
+ // The shared question helper already provides the same plain-readline
401
+ // fallback when a terminal cannot host the live TUI. Keeping a second
402
+ // prompt framework here pulled a large, unused transitive graph into every
403
+ // installation and made the fallback look different from the main CLI.
404
+ return askText({ title: 'You' });
403
405
  };
404
406
 
405
407
  const finish = () => { if (prompt) prompt.stop(); };
@@ -568,7 +570,7 @@ export async function interactiveConfig(options) {
568
570
  return;
569
571
  }
570
572
 
571
- // Asked in the product's own panels rather than inquirer's.
573
+ // Asked in the product's own panels rather than a second prompt framework.
572
574
  //
573
575
  // These were the last two surfaces on a second design system — different
574
576
  // colours, different glyphs, no NO_COLOR handling, and a list widget nothing
package/src/entrypoint.js CHANGED
@@ -5,6 +5,29 @@ import { runSelfUpdate } from './autoUpdate.js';
5
5
  import { printContextReport, printUsageReport, printCheckpoints, printUndo, buildStatusText, printReport } from './reporting.js';
6
6
  import { runCommand } from './commands.js';
7
7
  import { resolveInvocation } from './tui/invocation.js';
8
+ import { resolveUserPath } from './utils/paths.js';
9
+
10
+ function requestedWorkingDirectory(argv = []) {
11
+ const tokens = Array.isArray(argv) ? argv.slice(2) : [];
12
+ for (let index = 0; index < tokens.length; index++) {
13
+ const token = String(tokens[index]);
14
+ if (token === '--') break;
15
+ if (token === '-C' || token === '--cwd') return tokens[index + 1] || '';
16
+ if (token.startsWith('--cwd=')) return token.slice('--cwd='.length);
17
+ if (!token.startsWith('-')) break;
18
+ }
19
+ return null;
20
+ }
21
+
22
+ function applyRequestedWorkingDirectory(argv) {
23
+ const requested = requestedWorkingDirectory(argv);
24
+ if (requested === null) return;
25
+ if (!requested) throw new Error('The --cwd option requires a directory.');
26
+ const resolved = resolveUserPath(requested);
27
+ process.chdir(resolved);
28
+ }
29
+
30
+ applyRequestedWorkingDirectory(process.argv);
8
31
 
9
32
  // The old flag remains parseable only to explain its retirement.
10
33
  function announceDevEmulator(requested) {
@@ -42,6 +65,7 @@ program
42
65
  // "Usage: causyne-agent" reasonably concludes they ran the wrong thing.
43
66
  .name('causyne')
44
67
  .description('Causyne — Local desktop AI harness (alias: causyne-agent)')
68
+ .option('-C, --cwd <directory>', 'Use this directory as the workspace')
45
69
  .version(pkgVersion);
46
70
 
47
71
  program
package/src/sandbox.js CHANGED
@@ -96,13 +96,10 @@ const INTERPRETER_INVOCATION = /(^|\s)(sh|bash|zsh|ksh|dash|fish|eval|xargs|ssh|
96
96
  /**
97
97
  * Credential stores and key material.
98
98
  *
99
- * L4 (local-path audit): the DEFAULT workspace scope ends with the home
100
- * directory itself as a fallback, which silently puts ~/.ssh, ~/.aws and
101
- * ~/.causyne (the encrypted API key AND the .vault_key that decrypts it) inside
102
- * the agent's reach with no prompt. Vision section 4 asks for freedom WITH a
103
- * human in the loop rather than a wall, so these are not blocked — touching one
104
- * is classified 'extreme', which always requires explicit approval in every
105
- * autonomy mode, including Mode C.
99
+ * A user may deliberately select a workspace that contains credential material.
100
+ * These paths are not silently trusted just because they sit below that root:
101
+ * touching one is classified 'extreme', which always requires explicit approval
102
+ * in every autonomy mode, including Mode C.
106
103
  */
107
104
  const CREDENTIAL_PATHS = [
108
105
  /(^|\/)\.ssh(\/|$)/,
@@ -168,22 +165,17 @@ function redirectsIntoSensitiveTarget(maskedCommand) {
168
165
  }
169
166
 
170
167
  export class Sandbox {
171
- constructor() {
168
+ constructor({ workspaceRoot = process.cwd() } = {}) {
172
169
  this.config = loadConfig();
173
-
174
- // V5 Upgrade: Expanded default workspace paths
175
- // Instead of only os.homedir() (which was too restrictive and caused path errors),
176
- // we now default to common user directories while still blocking system dirs.
177
- const home = os.homedir();
178
- this.workspacePaths = this.config.workspacePaths || [
179
- path.join(home, 'Desktop'),
180
- path.join(home, 'Documents'),
181
- path.join(home, 'Downloads'),
182
- path.join(home, 'Projects'),
183
- path.join(home, 'CausyneWorkspace'),
184
- path.join(home, 'Causyne'),
185
- home // Fallback: allow home dir itself (covers edge cases like ~/scripts, ~/.config)
186
- ];
170
+
171
+ // The launch directory is the workspace. A previous default advertised
172
+ // Desktop, Documents, Downloads, ~/Causyne and the whole home directory as
173
+ // equally valid roots. That made the model drift into the agent's own source
174
+ // even when the user launched it from another project, while also making the
175
+ // sandbox effectively home-wide. Explicit runtime grants can still use
176
+ // updateWorkspacePaths(), but stale config never overrides the folder the
177
+ // user selected for this invocation.
178
+ this.workspacePaths = [path.resolve(workspaceRoot)];
187
179
 
188
180
  // Developer emulator mode (hardened, secret-gated — see devMode.js) grants
189
181
  // full local access for agent development on a Fast-tier emulator. It cannot
@@ -243,9 +235,9 @@ export class Sandbox {
243
235
  }
244
236
  }
245
237
 
246
- // L4: a credential store is in reach by default (the home fallback), so it
247
- // gets a human in the loop rather than a wall. 'extreme' is the one risk
248
- // level Mode C cannot auto-approve.
238
+ // L4: when an explicitly selected workspace contains credential material,
239
+ // it gets a human in the loop rather than silent access. 'extreme' is the
240
+ // one risk level Mode C cannot auto-approve.
249
241
  for (const p of pathsToCheck) {
250
242
  if (typeof p === 'string' && isCredentialPath(p)) {
251
243
  return { allowed: true, risk: 'extreme', reason: `Touches credential material: ${p}` };
@@ -352,20 +344,6 @@ export class Sandbox {
352
344
  return false;
353
345
  }
354
346
 
355
- // V7 Ultimate Self-Preservation: Block access to the agent's own source code
356
- // The AGI cannot read, modify, or delete causyne-agent itself.
357
- const agentBinOrScript = process.argv[1] || '';
358
- let agentRoot = '';
359
- if (agentBinOrScript && agentBinOrScript.includes('causyne-agent')) {
360
- const idx = agentBinOrScript.lastIndexOf('causyne-agent');
361
- agentRoot = path.resolve(agentBinOrScript.slice(0, idx + 'causyne-agent'.length));
362
- } else if (typeof __dirname !== 'undefined') {
363
- agentRoot = path.resolve(__dirname, '..');
364
- }
365
- if (agentRoot && resolved.startsWith(agentRoot) && resolved !== path.resolve(os.homedir())) {
366
- return false;
367
- }
368
-
369
347
  // V6: Override workspace limits if grantAllAccess is active
370
348
  if (this.grantAllAccess) {
371
349
  return true;
package/src/tui/ask.js CHANGED
@@ -2,8 +2,8 @@
2
2
  //
3
3
  // `causyne config` and `causyne pair` run with no chat session behind them, so
4
4
  // there is no live region for a dialog to draw into. That is why they were the
5
- // last two surfaces still on inquirer — and inquirer is a different design
6
- // system: its own colours, its own glyphs, no NO_COLOR handling, and a list
5
+ // last two surfaces still on a second prompt framework, with its own colours,
6
+ // glyphs, NO_COLOR handling, and list
7
7
  // widget that looks nothing like the picker the same user sees inside `causyne`.
8
8
  //
9
9
  // The fix is NOT a second app shell. That was tried once: screen.js, layout.js,
package/src/tui/banner.js CHANGED
@@ -23,9 +23,44 @@ const WORDMARK = [
23
23
  ' ████ █ █ ███ ████ █ █ █ █████'
24
24
  ];
25
25
 
26
- // A terminal without box-drawing support would render the art as mojibake, so
27
- // it gets the wordmark spelled out instead of a field of question marks.
28
- const WORDMARK_PLAIN = ['C A U S Y N E'];
26
+ // A compact terminal rendering of the six-part Causyne aperture. The shape is
27
+ // sampled from the canonical SVG rather than invented as a second logo. Its
28
+ // upper-right blade carries the signal green; the other blades use the neutral
29
+ // brand ink. Each row remains ordinary terminal text and degrades to #/+ ASCII.
30
+ const LOGO_SEGMENTS = [
31
+ [['ink', ' ███▓']],
32
+ [['ink', ' ▓███'], ['accent', '███'], ['ink', '▓']],
33
+ [['ink', '▓███ ▓▓'], ['accent', '██'], ['ink', '▓']],
34
+ [['ink', '▓██ ▓██▓']],
35
+ [['ink', '▓███▓ ███▓']],
36
+ [['ink', ' ███████▓']],
37
+ [['ink', ' ▓███']]
38
+ ];
39
+
40
+ function logoRows(unicode) {
41
+ return LOGO_SEGMENTS.map((segments) => segments.map(([role, text]) => {
42
+ const rendered = unicode
43
+ ? text
44
+ : text.replace(/[█▓]/g, role === 'accent' ? '+' : '#');
45
+ return paint(role, rendered);
46
+ }).join(''));
47
+ }
48
+
49
+ function brandArt(width, unicode) {
50
+ if (!unicode || width < 64) {
51
+ const logo = logoRows(false);
52
+ logo[2] = `${padEnd(logo[2], 12)}${bold(paint('ink', 'CAUSYNE AGENT'))}`;
53
+ return logo;
54
+ }
55
+
56
+ const logo = logoRows(true);
57
+ return logo.map((line, index) => {
58
+ const word = WORDMARK[index - 1];
59
+ return word === undefined
60
+ ? line
61
+ : `${padEnd(line, 10)} ${paint('ink', word)}`;
62
+ });
63
+ }
29
64
 
30
65
  /**
31
66
  * The banner rows.
@@ -35,10 +70,9 @@ const WORDMARK_PLAIN = ['C A U S Y N E'];
35
70
  * never be exercised at all.
36
71
  */
37
72
  export function wordmark(width = 80, { unicode = HAS_UNICODE } = {}) {
38
- const art = (unicode && width >= 54) ? WORDMARK : WORDMARK_PLAIN;
39
- const rows = art.map((line) => paint('accent', line));
73
+ const rows = brandArt(width, unicode);
40
74
  rows.push('');
41
- rows.push(dim('Causyne AI Agent'));
75
+ rows.push(dim('Causyne Agent'));
42
76
  return rows;
43
77
  }
44
78
 
@@ -60,8 +94,8 @@ export function wordmark(width = 80, { unicode = HAS_UNICODE } = {}) {
60
94
  * header that wraps is worse than a header that is tall.
61
95
  */
62
96
  export function sessionBanner(info = {}, width = 80, { unicode = HAS_UNICODE } = {}) {
63
- const art = (unicode && width >= 54) ? WORDMARK : WORDMARK_PLAIN;
64
- const artWidth = Math.max(...art.map(visibleWidth));
97
+ let art = brandArt(width, unicode);
98
+ let artWidth = Math.max(...art.map(visibleWidth));
65
99
 
66
100
  const facts = [];
67
101
  if (info.version) facts.push([bold(paint('accent', 'Causyne')), dim(info.version)]);
@@ -78,11 +112,22 @@ export function sessionBanner(info = {}, width = 80, { unicode = HAS_UNICODE } =
78
112
  // deferred-wrap state: the cursor sits past the last column, and the next
79
113
  // write decides whether that becomes a wrap. Measured on an 80-column pty,
80
114
  // the workspace row came back at exactly 80 while every other row was 79.
81
- const room = width - artWidth - GAP - 1;
115
+ let room = width - artWidth - GAP - 1;
116
+
117
+ // A coloured 80-column terminal can render the full block wordmark, but it
118
+ // leaves too little room for the session facts beside it. Stacking those
119
+ // facts made startup 16 rows tall and overflowed short terminals. Prefer the
120
+ // compact ASCII aperture in that case: identity and facts remain one object,
121
+ // and the first prompt still fits without scrolling the banner away.
122
+ if (room < 24 && unicode && width >= 64) {
123
+ art = brandArt(width, false);
124
+ artWidth = Math.max(...art.map(visibleWidth));
125
+ room = width - artWidth - GAP - 1;
126
+ }
82
127
 
83
128
  // Not enough width for two columns: stack, rather than wrap either one.
84
129
  if (room < 24) {
85
- return art.map((line) => paint('accent', line))
130
+ return art
86
131
  .concat([''], facts.map(([left, right]) =>
87
132
  truncate(right ? `${left} ${right}` : left, width - 1, GLYPHS.ellipsis)));
88
133
  }
@@ -90,7 +135,7 @@ export function sessionBanner(info = {}, width = 80, { unicode = HAS_UNICODE } =
90
135
  const rows = [];
91
136
  const height = Math.max(art.length, facts.length);
92
137
  for (let index = 0; index < height; index++) {
93
- const artLine = art[index] === undefined ? '' : paint('accent', art[index]);
138
+ const artLine = art[index] === undefined ? '' : art[index];
94
139
  const fact = facts[index];
95
140
  const left = padEnd(artLine, artWidth) + ' '.repeat(GAP);
96
141
  if (!fact) { rows.push(artLine); continue; }
@@ -299,9 +299,9 @@ export class TextLayer extends Layer {
299
299
  * how dangerous is this, what exactly will it do, and what will change on disk.
300
300
  * The diff scrolls, so a 400-line rewrite is inspectable rather than truncated.
301
301
  *
302
- * Deliberately has NO default. `y` and `n` are explicit, enter is not bound, and
303
- * escape denies. A dialog where the safe answer is one keypress away from the
304
- * dangerous one is how a person approves something they meant to read.
302
+ * The highlighted default is Reject. `y` and `n` remain explicit shortcuts;
303
+ * arrows choose a visible option and Enter commits it. A reflex Enter therefore
304
+ * denies, while an intentional move to either Allow option works consistently.
305
305
  */
306
306
  export class ApprovalLayer extends Layer {
307
307
  constructor(options = {}) {
@@ -495,7 +495,10 @@ export class ConfirmLayer extends Layer {
495
495
  super({ width: options.width || 60, height: options.height || 8, ...options });
496
496
  this.message = options.message || 'Are you sure?';
497
497
  this.onAnswer = options.onAnswer || null;
498
- this.footer = `y yes ${GLYPHS.dot} n no ${GLYPHS.dot} esc cancel`;
498
+ this.defaultAnswer = options.defaultAnswer === true;
499
+ this.footer = this.defaultAnswer
500
+ ? `enter confirm ${GLYPHS.dot} y yes ${GLYPHS.dot} n no ${GLYPHS.dot} esc cancel`
501
+ : `enter cancel ${GLYPHS.dot} y yes ${GLYPHS.dot} n no ${GLYPHS.dot} esc cancel`;
499
502
  this.settled = false;
500
503
  }
501
504
 
@@ -523,10 +526,15 @@ export class ConfirmLayer extends Layer {
523
526
 
524
527
  key(event) {
525
528
  if (!event) return null;
526
- // Only an explicit `y` approves. Every other key either says no or is
527
- // ignored — a mistyped keypress must never approve.
529
+ // `y` is always an explicit approval. Enter follows the default selected by
530
+ // the caller: false for ordinary destructive confirmations, true only for
531
+ // the second step after the user has already selected Allow always.
528
532
  if (isChar(event, 'y')) { this.answer(true); return 'close'; }
529
533
  if (isChar(event, 'n') || event.name === 'escape') { this.answer(false); return 'close'; }
534
+ if (event.name === 'enter' || event.name === 'return') {
535
+ this.answer(this.defaultAnswer);
536
+ return 'close';
537
+ }
530
538
  return 'handled';
531
539
  }
532
540
  }
@@ -34,6 +34,8 @@ export const ROOT_OPTIONS = new Map([
34
34
  ['--help', 0],
35
35
  ['-V', 0],
36
36
  ['--version', 0],
37
+ ['-C', 1],
38
+ ['--cwd', 1],
37
39
  ['--dev-emulator', 0]
38
40
  ]);
39
41
 
package/src/tui/keys.js CHANGED
@@ -273,6 +273,17 @@ export class KeyDecoder {
273
273
  } else if (final === 'u') {
274
274
  // Kitty keyboard protocol: ESC [ codepoint ; modifiers u
275
275
  const codepoint = numbers[0];
276
+ const controlName = {
277
+ 9: 'tab',
278
+ 10: 'enter',
279
+ 13: 'enter',
280
+ 27: 'escape',
281
+ 127: 'backspace'
282
+ }[codepoint];
283
+ if (controlName) {
284
+ events.push(applyModifier(makeKey(controlName, { sequence }), numbers[1]));
285
+ return sequence.length;
286
+ }
276
287
  if (Number.isFinite(codepoint) && codepoint >= 32) {
277
288
  const character = String.fromCodePoint(codepoint);
278
289
  const key = applyModifier(
package/src/tui/prompt.js CHANGED
@@ -12,7 +12,7 @@
12
12
  // * ESC INTERRUPTS — wired to the AgentLoop.abort() that already existed and
13
13
  // was only reachable by typing /stop after the turn had finished.
14
14
  // * FAIL OPEN — if raw mode is unavailable (piped stdin, a CI runner, an
15
- // unusual terminal) the caller falls back to inquirer. A fancy editor must
15
+ // unusual terminal) the caller falls back to plain readline. A fancy editor must
16
16
  // never be the reason chat does not start.
17
17
  //
18
18
  // Pure Node. No dependency, no shell.
@@ -444,12 +444,15 @@ export class ChatPrompt {
444
444
  }
445
445
 
446
446
  /** A plain yes/no, for the questions that are not tool approvals. */
447
- confirm(message) {
447
+ confirm(message, options = {}) {
448
448
  return new Promise((resolve) => {
449
449
  let settled = false;
450
450
  const settle = (answer) => { if (!settled) { settled = true; resolve(Boolean(answer)); } };
451
- const layer = new ConfirmLayer({ message, onAnswer: settle });
452
- layer.answer = () => settle(false);
451
+ const layer = new ConfirmLayer({
452
+ message,
453
+ defaultAnswer: options.defaultAnswer === true,
454
+ onAnswer: settle
455
+ });
453
456
  this.openLayer(layer);
454
457
  });
455
458
  }
package/src/tui/theme.js CHANGED
@@ -90,52 +90,54 @@ export const HAS_UNICODE = HAS_COLOR
90
90
  // Roles, not colours. A caller asking for `danger` should never care whether
91
91
  // that is truecolor crimson or plain ANSI red.
92
92
  const DARK_PALETTE = {
93
- accent: [ 96, 205, 255],
94
- agent: [138, 180, 248],
95
- user: [ 90, 200, 160],
96
- success: [126, 199, 108],
93
+ // Causyne brand palette from the canonical site/logo reference:
94
+ // signal #b6e06f, text #e9edf2, muted #8d96a4, line #272c35,
95
+ // panel #161920 and background #0f1115.
96
+ accent: [182, 224, 111],
97
+ agent: [182, 224, 111],
98
+ user: [154, 199, 86],
99
+ success: [182, 224, 111],
97
100
  warn: [230, 190, 90],
98
101
  danger: [235, 110, 110],
99
- muted: [130, 138, 150],
100
- ink: [225, 228, 234],
101
- rule: [ 62, 68, 80],
102
- // The scroll rail. A deliberately DARK blue, not the accent.
102
+ muted: [141, 150, 164],
103
+ ink: [233, 237, 242],
104
+ rule: [ 39, 44, 53],
105
+ // The scroll rail uses a deliberately darker brand green than the accent.
103
106
  //
104
107
  // The rail is orientation, not information you act on — it says where you are
105
108
  // while you read something else, so it must be legible without competing with
106
- // the transcript for attention. In `accent` cyan it was the brightest thing on
107
- // a screen full of prose. Dark enough to recede, blue enough to read as one
108
- // continuous object beside the markers, which keep their own roles.
109
- scroll: [ 58, 110, 170],
109
+ // the transcript for attention. Dark enough to recede, while remaining part
110
+ // of the same graphite-and-signal-green visual system as the rest of the CLI.
111
+ scroll: [ 99, 125, 63],
110
112
  // Background roles. opencode fills its input surface with theme
111
113
  // backgroundElement and marks it with a left border in the agent's colour
112
114
  // (component/prompt/index.tsx: border={["left"]} + backgroundColor). These are
113
115
  // that fill: one step off the terminal's own background, never a block of
114
116
  // colour that fights the text sitting on it.
115
- surface: [ 28, 31, 38],
116
- surfaceHi:[ 38, 42, 51]
117
+ surface: [ 22, 25, 32],
118
+ surfaceHi:[ 27, 31, 39]
117
119
  };
118
120
 
119
121
  const LIGHT_PALETTE = {
120
- accent: [ 0, 110, 170],
121
- agent: [ 40, 80, 170],
122
- user: [ 20, 120, 90],
123
- success: [ 30, 120, 40],
122
+ accent: [ 91, 130, 29],
123
+ agent: [ 91, 130, 29],
124
+ user: [ 74, 112, 26],
125
+ success: [ 74, 112, 26],
124
126
  warn: [155, 110, 0],
125
127
  danger: [185, 40, 40],
126
- muted: [ 95, 102, 112],
127
- ink: [ 25, 28, 34],
128
- rule: [200, 204, 212],
129
- scroll: [ 60, 100, 160],
128
+ muted: [105, 112, 123],
129
+ ink: [ 58, 63, 71],
130
+ rule: [198, 203, 211],
131
+ scroll: [ 91, 130, 29],
130
132
  surface: [242, 244, 247],
131
133
  surfaceHi:[232, 235, 240]
132
134
  };
133
135
 
134
136
  // Fallback for 16-colour terminals: the closest base ANSI code per role.
135
137
  const BASIC_CODES = {
136
- accent: 36, agent: 34, user: 32, success: 32,
138
+ accent: 92, agent: 92, user: 32, success: 92,
137
139
  warn: 33, danger: 31, muted: 90, ink: 39, rule: 90,
138
- scroll: 34,
140
+ scroll: 32,
139
141
  surface: 39, surfaceHi: 39
140
142
  };
141
143
 
@@ -1,12 +0,0 @@
1
- import rawInquirer from 'inquirer';
2
-
3
-
4
- const inquirer = rawInquirer?.prompt
5
- ? rawInquirer
6
- : (rawInquirer?.default?.prompt
7
- ? rawInquirer.default
8
- : (rawInquirer?.default?.default?.prompt
9
- ? rawInquirer.default.default
10
- : rawInquirer));
11
-
12
- export default inquirer;