kritya 0.8.25-beta → 0.8.27-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,7 +55,7 @@ review → fix` for building something new end-to-end
55
55
 
56
56
  ## Setup
57
57
 
58
- Requires Node.js >=22.
58
+ Requires Node.js >=22.19.0.
59
59
 
60
60
  1. Get an API key from your chosen provider (click any
61
61
  model → "Get API Key").
@@ -170,7 +170,8 @@ In-session commands (type `/` to see them with autocomplete; letters filter the
170
170
  Custom `/commands` you define (see [docs/CONFIGURATION.md](docs/CONFIGURATION.md#custom-slash-commands)) also appear here.
171
171
 
172
172
  `Esc` cancels a running request. `↑/↓` recalls input history. `Ctrl+O` toggles
173
- full tool output. `Ctrl+K` is the kill switch (see below). `Ctrl+C` exits.
173
+ full tool output. `Ctrl+E` opens the file the agent touched most recently in
174
+ `$EDITOR`. `Ctrl+K` is the kill switch (see below). `Ctrl+C` exits.
174
175
 
175
176
  ### More features
176
177
 
@@ -45,6 +45,15 @@ export class Agent {
45
45
  dryRunMode = false;
46
46
  /** When true, file-edit tools auto-approve without prompting (see ACCEPT_EDITS_TOOL_NAMES). */
47
47
  acceptEdits = false;
48
+ /**
49
+ * When true, EVERY tool call auto-approves without prompting — including
50
+ * `shell` and classifyDanger-flagged destructive commands, unlike
51
+ * `acceptEdits`. Only reachable from the UI when the sandbox is active
52
+ * (see sandboxAvailable in shell/sandbox.ts), since that's the safety net
53
+ * this mode leans on instead of a human in the loop. Deny rules and the
54
+ * kill switch still apply regardless of this flag.
55
+ */
56
+ bypassMode = false;
48
57
  /**
49
58
  * Whether a human can see and respond to a permission prompt right now.
50
59
  * True by default (the CLI and its subagents render one); headless runs
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
+ import { writeStderr } from "../stderr.js";
4
5
  import { pluginsDir, pluginSkillsRoots, scanPlugins, userPluginsDir } from "../plugins/discover.js";
5
6
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
6
7
  const KEY_VALUE_RE = /^([A-Za-z0-9_-]+):\s*(.*)$/;
@@ -73,7 +74,7 @@ function unquote(value) {
73
74
  return value;
74
75
  }
75
76
  let warnSink = (message) => {
76
- process.stderr.write(`kritya: ${message}\n`);
77
+ writeStderr(`kritya: ${message}\n`);
77
78
  };
78
79
  function warn(message) {
79
80
  warnSink(message);
@@ -236,8 +236,17 @@ export class ToolExecutor {
236
236
  danger === null &&
237
237
  tool.requiresPermission &&
238
238
  ACCEPT_EDITS_TOOL_NAMES.has(tool.name);
239
+ // Unlike autoApproveEdit, this covers every tool — including `shell` and
240
+ // classifyDanger-flagged destructive commands — since bypassMode leans on
241
+ // the sandbox (not a human) to contain the blast radius. planMode/dryRunMode
242
+ // already returned above, so this can't apply while either is on.
243
+ const autoApproveBypass = host.bypassMode;
239
244
  let source;
240
- if (autoApproveEdit) {
245
+ if (autoApproveBypass) {
246
+ host.onAutoApprove?.();
247
+ source = "bypass-mode";
248
+ }
249
+ else if (autoApproveEdit) {
241
250
  host.onAutoApprove?.();
242
251
  source = "accept-edits";
243
252
  }
@@ -232,6 +232,10 @@ async function runPhase(ctx, phase) {
232
232
  ctx.agent.acceptEdits = false;
233
233
  ctx.setAcceptEdits(false);
234
234
  }
235
+ if (wantPlanMode && ctx.bypassMode) {
236
+ ctx.agent.bypassMode = false;
237
+ ctx.setBypassMode(false);
238
+ }
235
239
  saveProjectState(ctx.workspace, project.name, phase);
236
240
  const artifact = artifactPath(project.name, phase);
237
241
  ctx.addItem({
@@ -161,6 +161,8 @@ export function resolveProvider(config, override) {
161
161
  apiKey = merged.apiKey;
162
162
  if (!apiKey && name === "nvidia")
163
163
  apiKey = process.env.NVIDIA_API_KEY || config.apiKey;
164
+ if (apiKey)
165
+ apiKey = apiKey.trim();
164
166
  return {
165
167
  name,
166
168
  baseUrl,
@@ -1,3 +1,4 @@
1
+ import { writeStderr } from "../stderr.js";
1
2
  /**
2
3
  * Most best-effort persistence (session/audit/telemetry writes, config and
3
4
  * trust-file reads, hook loading, …) deliberately swallows its own errors —
@@ -10,12 +11,7 @@ export function debugLog(context, err) {
10
11
  if ((process.env.KRITYA_DEBUG ?? "").toLowerCase() !== "1" && process.env.KRITYA_DEBUG !== "true")
11
12
  return;
12
13
  const message = err instanceof Error ? (err.stack ?? err.message) : String(err);
13
- try {
14
- process.stderr.write(`[kritya debug] ${context}: ${message}\n`);
15
- }
16
- catch {
17
- // stderr itself failing isn't something debug logging can do anything about
18
- }
14
+ writeStderr(`[kritya debug] ${context}: ${message}\n`);
19
15
  }
20
16
  /**
21
17
  * For best-effort persistence that a user actually needs to know failed
@@ -39,12 +35,7 @@ export function warnUser(context, err) {
39
35
  }
40
36
  warnedContexts.add(context);
41
37
  const message = err instanceof Error ? err.message : String(err);
42
- try {
43
- process.stderr.write(`[kritya] warning: ${context} failed: ${message}\n`);
44
- }
45
- catch {
46
- // stderr itself failing isn't something this can do anything about
47
- }
38
+ writeStderr(`[kritya] warning: ${context} failed: ${message}\n`);
48
39
  debugLog(context, err);
49
40
  }
50
41
  /** Contexts currently warning via warnPersistenceFailure, in the order first seen. */
@@ -45,6 +45,24 @@ export const CURATED_MODELS = [
45
45
  note: "strong agentic tool use",
46
46
  contextWindow: 256_000,
47
47
  },
48
+ {
49
+ id: "z-ai/glm-5.3",
50
+ label: "GLM 5.3",
51
+ note: "strong agentic tool use",
52
+ contextWindow: 1_048_576,
53
+ },
54
+ {
55
+ id: "z-ai/glm-5.3-flash",
56
+ label: "GLM 5.3 Flash",
57
+ note: "fast + cheap",
58
+ contextWindow: 1_048_576,
59
+ },
60
+ {
61
+ id: "deepseek-ai/deepseek-v4.1-flash",
62
+ label: "DeepSeek V4.1 Flash",
63
+ note: "fast + cheap",
64
+ contextWindow: 1_048_576,
65
+ },
48
66
  ];
49
67
  export const DEFAULT_MODEL = CURATED_MODELS[0].id;
50
68
  /**
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import { AuditLog } from "./audit/audit.js";
16
16
  import { runAuditCli } from "./audit/cli.js";
17
17
  import { runSkillsCli } from "./agent/skillsCli.js";
18
18
  import { createTracer, cleanupOldTelemetry } from "./telemetry/tracer.js";
19
- import { createMeter } from "./telemetry/metrics.js";
19
+ import { createMeter, RENDER_BOUNDS_MS } from "./telemetry/metrics.js";
20
20
  import { retentionDaysFor } from "./config/retention.js";
21
21
  import { backgroundManager } from "./shell/background.js";
22
22
  import { defaultSandboxMode, sandboxAvailable, sandboxUnavailableReason } from "./shell/sandbox.js";
@@ -415,6 +415,17 @@ async function main() {
415
415
  sessionMeter.flushAndWait(),
416
416
  new Promise((resolve) => setTimeout(resolve, 2000).unref()),
417
417
  ]);
418
+ // Hand the terminal back before leaving. Ink hides the cursor for the
419
+ // whole time it is rendering and only shows it again from its own
420
+ // teardown — which the process.exit() below skips, leaving the shell's
421
+ // prompt invisible until the user runs `reset`. unmount() is that
422
+ // teardown; it also finishes drawing the frame we are standing on.
423
+ try {
424
+ ui.instance?.unmount();
425
+ }
426
+ catch {
427
+ // Whatever brought us here may have been the UI itself. Exit anyway.
428
+ }
418
429
  cleanup();
419
430
  process.exit(code);
420
431
  })();
@@ -709,12 +720,24 @@ async function main() {
709
720
  // compaction — subagents (read-only or write) never do, even though they
710
721
  // run the same Agent class and can also trigger auto-compaction.
711
722
  agent.autoMemory = true;
723
+ // How long Ink takes to turn the React tree into a frame, per frame. This is
724
+ // the part of drawing a screen that grows with the size of the transcript, so
725
+ // it is the number that says whether the UI is getting sluggish — the
726
+ // terminal write it excludes is bounded by the terminal, not by us.
727
+ // `renderTime` excludes Ink's own throttling, so it is the raw cost of the
728
+ // frame rather than the interval between frames.
729
+ const renderHistogram = sessionMeter.histogram("kritya.render.duration_ms", RENDER_BOUNDS_MS);
712
730
  ui.instance = render(_jsx(App, { agent: agent, workspace: workspace, modelRef: modelRef, providerRef: providerRef, config: config, resumedCount: initialHistory.length, initialTasks: initialTasks, undoStack: undoStack, uiBridge: uiBridge, resumeSessions: resumeSessions.length ? resumeSessions : undefined, customCommands: loadCustomCommands(workspace, trustWorkspace, plugins), mcpToolCount: mcpTools.length, onSwitchClient: (newClient) => {
713
731
  client = newClient;
714
732
  }, onRequestPermissionReady: (fn) => {
715
733
  permissionRef.current = fn;
716
734
  }, onRequestElicitationReady: (fn) => {
717
735
  elicitationRef.current = fn;
718
- }, privacyMode: privacyMode, firstLaunch: firstLaunch }));
736
+ }, privacyMode: privacyMode, firstLaunch: firstLaunch }), {
737
+ onRender: ({ renderTime }) => renderHistogram.record(renderTime),
738
+ // Repaint only the lines that changed instead of the whole frame:
739
+ // less flicker and less CPU while streaming. Ink 8 native.
740
+ incrementalRendering: true,
741
+ });
719
742
  markBannerSeen(workspace);
720
743
  }
@@ -1,6 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
+ import { writeStderr } from "../stderr.js";
4
5
  import { VERSION } from "../version.js";
5
6
  import { NOOP_TRACER } from "../telemetry/tracer.js";
6
7
  import { McpAuthRequiredError } from "./oauth.js";
@@ -644,14 +645,14 @@ function exposedToolName(server, toolName) {
644
645
  * is registered on the transport so `tools/call` can mirror argument values
645
646
  * into `Mcp-Param-{Name}` headers later — see `ModernHttpTransport.
646
647
  * setToolHeaderMap`/`send`. Logged the same way other "skip with a warning"
647
- * MCP failures are (`process.stderr.write("kritya: ...")`).
648
+ * MCP failures are (`writeStderr("kritya: ...")`).
648
649
  */
649
650
  function filterHeaderAnnotatedTools(server, specs, transport) {
650
651
  const kept = [];
651
652
  for (const spec of specs) {
652
653
  const result = validateToolHeaders(spec.inputSchema);
653
654
  if (!result.ok) {
654
- process.stderr.write(`kritya: MCP server "${server}" tool "${spec.name}" excluded — invalid x-mcp-header ` +
655
+ writeStderr(`kritya: MCP server "${server}" tool "${spec.name}" excluded — invalid x-mcp-header ` +
655
656
  `annotation: ${result.reason}\n`);
656
657
  continue;
657
658
  }
@@ -675,7 +676,7 @@ function filterSchemaSafeTools(server, specs) {
675
676
  for (const spec of specs) {
676
677
  const result = checkSchemaSafety(spec.inputSchema);
677
678
  if (!result.ok) {
678
- process.stderr.write(`kritya: MCP server "${server}" tool "${spec.name}" excluded — unsafe inputSchema: ` +
679
+ writeStderr(`kritya: MCP server "${server}" tool "${spec.name}" excluded — unsafe inputSchema: ` +
679
680
  `${result.reason}\n`);
680
681
  continue;
681
682
  }
@@ -852,7 +853,7 @@ export async function connectServer(name, cfg, trace) {
852
853
  }
853
854
  else {
854
855
  status.error = err instanceof Error ? err.message : String(err);
855
- process.stderr.write(`kritya: MCP server "${name}" failed to start: ${status.error}\n`);
856
+ writeStderr(`kritya: MCP server "${name}" failed to start: ${status.error}\n`);
856
857
  span.setStatus("ERROR", status.error);
857
858
  // status.error comes from the (untrusted) server process's own error
858
859
  // output, which could echo back an env var or header value it was
@@ -1,5 +1,5 @@
1
1
  import { lookup as dnsLookup } from "node:dns/promises";
2
- import { Agent } from "undici";
2
+ import { Agent, Dispatcher1Wrapper } from "undici";
3
3
  /**
4
4
  * Shared "is this host on a private/internal network" check, used by both
5
5
  * fetch_url (src/tools/fetchUrl.ts) and the MCP HTTP transport guard
@@ -210,7 +210,7 @@ export function assertSafeUrl(label, url) {
210
210
  * it's safe to share across every call site that uses the same policy.
211
211
  */
212
212
  function createPinnedDispatcher(isBlockedAddress) {
213
- return new Agent({
213
+ return new Dispatcher1Wrapper(new Agent({
214
214
  connect: {
215
215
  lookup(hostname, options, callback) {
216
216
  dnsLookup(hostname, { all: true })
@@ -235,7 +235,7 @@ function createPinnedDispatcher(isBlockedAddress) {
235
235
  .catch((err) => callback(err, []));
236
236
  },
237
237
  },
238
- });
238
+ }));
239
239
  }
240
240
  /** For callers that never allow loopback either (fetch_url). */
241
241
  export const pinnedDispatcher = createPinnedDispatcher(isPrivateOrLoopbackHost);
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
+ import { writeStderr } from "../stderr.js";
4
5
  /**
5
6
  * Scans each root for `<name>/plugin.json` folders, in name order, and reports
6
7
  * both what loaded and why anything didn't. Roots that don't exist are
@@ -73,7 +74,7 @@ export function scanPluginsDetailed(roots) {
73
74
  return { loaded: [...seen.values()], skipped };
74
75
  }
75
76
  let warnSink = (message) => {
76
- process.stderr.write(`kritya: ${message}\n`);
77
+ writeStderr(`kritya: ${message}\n`);
77
78
  };
78
79
  function warn(message) {
79
80
  warnSink(message);
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { writeStderr } from "../stderr.js";
3
4
  function isServerConfig(v) {
4
5
  if (!v || typeof v !== "object")
5
6
  return false;
@@ -65,7 +66,7 @@ export function scanPluginMcpServers(plugins) {
65
66
  * map for labeling `/mcp` output. Skips are reported via `warn` (defaults to
66
67
  * stderr, like scanPlugins/scanSkills).
67
68
  */
68
- export function loadPluginMcpServers(plugins, warn = (m) => process.stderr.write(`kritya: ${m}\n`)) {
69
+ export function loadPluginMcpServers(plugins, warn = (m) => writeStderr(`kritya: ${m}\n`)) {
69
70
  const { loaded, skipped } = scanPluginMcpServers(plugins);
70
71
  for (const s of skipped) {
71
72
  warn(`skipping mcp server "${s.name}" from plugin "${s.pluginName}": ${s.reason}`);
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import fs from "node:fs/promises";
11
11
  import path from "node:path";
12
- import fg from "fast-glob";
12
+ import { glob } from "tinyglobby";
13
13
  import { isPathSafe, resolveSafe } from "../tools/common.js";
14
14
  import { loadIgnorePatterns } from "../tools/ignore.js";
15
15
  import { CODE_EXTENSIONS, extractSymbols } from "./symbols.js";
@@ -88,13 +88,13 @@ export async function buildRepoMap(workspace, subdir = ".", options = {}) {
88
88
  const opts = { ...DEFAULTS, ...options };
89
89
  const root = resolveSafe(workspace, subdir || ".");
90
90
  const scopeLabel = path.relative(workspace, root) || "(workspace root)";
91
- const found = await fg("**/*", {
91
+ const found = await glob("**/*", {
92
92
  cwd: root,
93
93
  dot: false,
94
94
  onlyFiles: true,
95
95
  followSymbolicLinks: false,
96
+ expandDirectories: false,
96
97
  ignore: ["**/node_modules/**", "**/.git/**", ...loadIgnorePatterns(workspace)],
97
- suppressErrors: true,
98
98
  });
99
99
  // Keep only source files, then rank by path so the maxFiles cap (which bounds
100
100
  // how many files we actually read) retains the most important ones.
package/dist/stderr.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Where a warning goes while someone else owns the terminal.
3
+ *
4
+ * Ink owns the terminal for the whole time the UI is up. It keeps its frame in
5
+ * a known place, and anything that reaches the terminal *through* Ink is
6
+ * written between clearing that frame and repainting it — so the text lands
7
+ * above the UI and stays there. A raw `process.stderr.write` bypasses all of
8
+ * that: the bytes land wherever the cursor happened to be, in the middle of
9
+ * the frame, and the next repaint erases and redraws against a line count that
10
+ * no longer matches the screen. The warning comes out garbled and takes the
11
+ * frame with it.
12
+ *
13
+ * Ink patches `console` for exactly this reason, but `console.error` is not
14
+ * available to the plain module functions that raise these warnings — an agent
15
+ * loop, an MCP client, a plugin loader — and a hook (`useStderr`) cannot be
16
+ * called from one. So the UI registers a sink on mount and clears it on
17
+ * unmount, and everything else goes through `writeStderr`, which uses the sink
18
+ * when there is one and raw stderr when there is not: CLI subcommands, headless
19
+ * runs, and anything before the UI mounts or after it is torn down.
20
+ *
21
+ * Deliberately not used by `crash.ts`: by the time a crash handler runs, the
22
+ * UI is being torn down and its frame state is not trustworthy, so the crash
23
+ * report goes straight to stderr.
24
+ */
25
+ let sink;
26
+ /** Installed by the mounted UI. Pass `undefined` on unmount. */
27
+ export function setStderrSink(fn) {
28
+ sink = fn;
29
+ }
30
+ /** Write a warning to stderr, through the UI when one is mounted. */
31
+ export function writeStderr(text) {
32
+ if (sink) {
33
+ try {
34
+ sink(text);
35
+ return;
36
+ }
37
+ catch {
38
+ // A sink that throws must not cost us the warning itself.
39
+ }
40
+ }
41
+ try {
42
+ process.stderr.write(text);
43
+ }
44
+ catch {
45
+ // stderr is gone; there is nowhere left to put it.
46
+ }
47
+ }
@@ -11,6 +11,17 @@ export const NOOP_METER = {
11
11
  stop() { },
12
12
  };
13
13
  export const DEFAULT_LATENCY_BOUNDS_MS = [10, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000];
14
+ /**
15
+ * Buckets for Ink's per-frame `onRender` timing.
16
+ *
17
+ * The default latency bounds are useless here: drawing a frame is a
18
+ * sub-millisecond to low-single-digit-millisecond affair, so every sample would
19
+ * land in the first bucket and the histogram would say nothing. These cluster
20
+ * below the frame budget instead, and 33ms is the edge that matters — Ink caps
21
+ * rendering at 30fps (1000 / 30 ≈ 33.3ms), so a sample past it is a frame the
22
+ * UI could not draw on time.
23
+ */
24
+ export const RENDER_BOUNDS_MS = [0.25, 0.5, 1, 2, 4, 8, 16, 24, 33, 66, 133];
14
25
  function nowUnixNano() {
15
26
  return (BigInt(Date.now()) * 1000000n).toString();
16
27
  }
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { CONFIG_DIR } from "../config/config.js";
5
5
  import { hardenWindowsDir } from "../config/winAcl.js";
6
6
  import { debugLog, warnPersistenceFailure } from "../config/debug.js";
7
+ import { writeStderr } from "../stderr.js";
7
8
  import { VERSION } from "../version.js";
8
9
  import { encodeSpan, postOtlp } from "./otlp.js";
9
10
  function nowUnixNano() {
@@ -164,12 +165,7 @@ function fileSink(file) {
164
165
  }
165
166
  function consoleSink() {
166
167
  return (span) => {
167
- try {
168
- process.stderr.write(`[otel] ${JSON.stringify(span)}\n`);
169
- }
170
- catch {
171
- // best-effort
172
- }
168
+ writeStderr(`[otel] ${JSON.stringify(span)}\n`);
173
169
  };
174
170
  }
175
171
  function resourceFor(sessionId) {
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import fg from "fast-glob";
2
+ import { glob } from "tinyglobby";
3
3
  import { isPathSafe, truncateResult, countLines } from "./common.js";
4
4
  import { loadIgnorePatterns } from "./ignore.js";
5
5
  export const globTool = {
@@ -17,14 +17,14 @@ export const globTool = {
17
17
  summarize: (args) => `Glob ${args.pattern}`,
18
18
  resultSummary: (output) => countLines(output, "match", "matches"),
19
19
  async execute(args, ctx) {
20
- // Models on Windows sometimes emit backslash paths; fast-glob needs forward slashes.
21
- const files = await fg(String(args.pattern).replaceAll("\\", "/"), {
20
+ // Models on Windows sometimes emit backslash paths; glob patterns need forward slashes.
21
+ const files = await glob(String(args.pattern).replaceAll("\\", "/"), {
22
22
  cwd: ctx.workspace,
23
23
  dot: false,
24
24
  onlyFiles: true,
25
25
  followSymbolicLinks: false,
26
+ expandDirectories: false,
26
27
  ignore: ["**/node_modules/**", "**/.git/**", ...loadIgnorePatterns(ctx.workspace)],
27
- suppressErrors: true,
28
28
  });
29
29
  const safeFiles = files.filter((f) => isPathSafe(ctx.workspace, path.join(ctx.workspace, f)));
30
30
  const capped = safeFiles.sort().slice(0, 200);
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import fg from "fast-glob";
3
+ import { glob } from "tinyglobby";
4
4
  import { isPathSafe, resolveSafe, safeCompileRegex, truncateResult, meaningfulLines, } from "./common.js";
5
5
  import { loadIgnorePatterns } from "./ignore.js";
6
6
  const MAX_MATCHES = 200;
@@ -34,14 +34,14 @@ export const grepTool = {
34
34
  async execute(args, ctx) {
35
35
  const regex = safeCompileRegex(String(args.pattern));
36
36
  const searchRoot = resolveSafe(ctx.workspace, String(args.path ?? "."));
37
- // Models on Windows sometimes emit backslash paths; fast-glob needs forward slashes.
38
- const files = await fg(String(args.include ?? "**/*").replaceAll("\\", "/"), {
37
+ // Models on Windows sometimes emit backslash paths; glob patterns need forward slashes.
38
+ const files = await glob(String(args.include ?? "**/*").replaceAll("\\", "/"), {
39
39
  cwd: searchRoot,
40
40
  dot: false,
41
41
  onlyFiles: true,
42
42
  followSymbolicLinks: false,
43
+ expandDirectories: false,
43
44
  ignore: ["**/node_modules/**", "**/.git/**", ...loadIgnorePatterns(ctx.workspace)],
44
- suppressErrors: true,
45
45
  });
46
46
  const matches = [];
47
47
  for (const file of files) {
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  const IGNORE_FILE = ".krityaignore";
4
- /** Converts one gitignore-style line into fast-glob ignore pattern(s). */
4
+ /** Converts one gitignore-style line into glob ignore pattern(s). */
5
5
  function toGlobPatterns(line) {
6
6
  let pattern = line.trim();
7
7
  if (!pattern || pattern.startsWith("#") || pattern.startsWith("!"))
@@ -15,7 +15,7 @@ function toGlobPatterns(line) {
15
15
  const base = anchored ? pattern : `**/${pattern}`;
16
16
  return isDir ? [`${base}/**`] : [base, `${base}/**`];
17
17
  }
18
- /** Reads `<workspace>/.krityaignore` (gitignore-style, one pattern per line) as fast-glob ignore patterns. */
18
+ /** Reads `<workspace>/.krityaignore` (gitignore-style, one pattern per line) as glob ignore patterns. */
19
19
  export function loadIgnorePatterns(workspace) {
20
20
  let raw;
21
21
  try {
@@ -19,5 +19,5 @@ export function AiDisclosurePrompt({ onDismiss }) {
19
19
  const id = setInterval(() => setBlinkOn((v) => !v), BLINK_INTERVAL_MS);
20
20
  return () => clearInterval(id);
21
21
  }, []);
22
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [_jsx(Text, { children: "kritya is an AI agent" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "You are interacting with an AI system: kritya reads/writes files and runs shell commands here, acting on an AI model's output \u2014 not a human." }) }), _jsx(Text, { children: "Mutating actions ask for approval first." }), _jsx(Text, { children: "Details: /help, SECURITY.md (EU AI Act Art. 50)." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Shown once. ", _jsx(Text, { color: "yellow", children: blinkOn ? "AI Disclosure" : " " })] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Press Enter to continue\u2026" }) })] }));
22
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [_jsx(Text, { children: "kritya is an AI agent" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "It reads files, edits your project, and runs commands \u2014 driven by an AI model, not a human. You approve changes before they happen." }) }), _jsx(Text, { dimColor: true, children: "Details: /help, SECURITY.md (EU AI Act Art. 50)." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Shown once. ", _jsx(Text, { color: "yellow", children: blinkOn ? "AI Disclosure" : " " })] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Press Enter to continue\u2026" }) })] }));
23
23
  }
@@ -1,7 +1,7 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
- import TextInput from "ink-text-input";
4
+ import { TextInput } from "./TextInput.js";
5
5
  /**
6
6
  * Shown once, only when the active provider has no API key resolved, on the
7
7
  * interactive TTY path (never from headless — see runHeadless in