@falkenslab/agent-kit 0.31.0 → 0.32.0

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
@@ -1,175 +1,175 @@
1
- <div align="center">
2
-
3
- <img src="https://raw.githubusercontent.com/falkenslab/agent-kit/main/docs/static/img/logo.svg" alt="agent-kit logo: three blue building blocks and a coral one being set in place" width="112"/>
4
-
5
- # agent-kit
6
-
7
- **Build an AI agent on Claude that asks before it acts, remembers, and chats like Claude Code.**<br/>
8
- You write what your agent knows and does; the kit brings oversight, safety, memory and the interface.
9
-
10
- [![npm](https://img.shields.io/npm/v/@falkenslab/agent-kit?color=cb3837&logo=npm)](https://www.npmjs.com/package/@falkenslab/agent-kit)
11
- ![Node.js 20+](https://img.shields.io/badge/node-20%2B-339933?logo=node.js&logoColor=white)
12
- ![Claude Agent SDK](https://img.shields.io/badge/Claude-Agent%20SDK-d97757?logo=anthropic&logoColor=white)
13
- ![TypeScript](https://img.shields.io/badge/types-included-3178c6?logo=typescript&logoColor=white)
14
- [![License: MIT](https://img.shields.io/badge/license-MIT-16a34a)](LICENSE)
15
-
16
- **[📖 Documentation](https://falkenslab.github.io/agent-kit/)** · [📖 Documentación en español](https://falkenslab.github.io/agent-kit/es/)
17
-
18
- </div>
19
-
20
- agent-kit is a foundation for building AI agents on top of the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk). It isn't an agent itself: it solves once what every agent needs (levels of oversight, approvals, safe limits, memory across sessions, extensions, and a terminal chat as good as Claude Code's), so each agent writes only what belongs to its own domain: what it knows how to do, with which tools, and how it talks. A teacher's assistant for Moodle and a pirate cat who tells jokes are built the same way.
21
-
22
- ## ✨ Why agent-kit
23
-
24
- - 🎚️ **You choose how much it acts alone.** Autonomous, guided (it asks before what others will see or can't be undone), step by step, or a plan mode where it only reads and plans; switch among them in the same conversation.
25
- - ✋ **It asks a person, and the kit makes sure.** Approvals, choices, a file, a sign-in by hand; an installed extension's tool that changes things waits for a yes whatever the model decided, and answers can last for the session or for good.
26
- - 🧠 **It remembers.** A knowledge base that grows with every session, the original documents it learns from kept untouched, a memory of the person, and every conversation to pick up again with `--continue` or `/resume`.
27
- - 🛡️ **It stays within limits.** Only its own folders, never protected files, subagents that can't slip past the main agent's limits, secrets hidden from the logs, remote servers allowed, asked about or denied.
28
- - 🧩 **It grows with extensions.** Claude Code plugins with MCP servers, skills and commands, installed from a marketplace or a folder, with their own options, sign-in and a catalog in the chat; no Git needed.
29
- - 🌐 **It speaks your language.** Its interface in English, Spanish, French or German, and it answers in that language unless you write in another.
30
- - 🖥️ **A terminal chat like Claude Code's, or none at all.** Full screen, live markdown, folded tool calls, panels, history, themes; or the same agent inside a desktop app or a web page with its own interface.
31
-
32
- ## 🚀 Quick start
33
-
34
- You need [Node.js](https://nodejs.org) 20 or later and a Claude account. In a project with `"type": "module"` in its `package.json`:
35
-
36
- ```bash
37
- npm install @falkenslab/agent-kit tsx
38
- ```
39
-
40
- Save this as `agent.ts`: the only part that's your agent's is the spec at the top.
41
-
42
- ```ts
43
- import path from "node:path";
44
- import { buildSessionOptions, ensureClaudeAuth, runChatInk, type AgentSpec, type BaseSessionConfig } from "@falkenslab/agent-kit";
45
-
46
- const spec: AgentSpec<BaseSessionConfig> = {
47
- buildSystemPrompt: () => "You are a pirate cat who tells jokes. Always answer in character.",
48
- buildMcpServers: () => ({}), // its own MCP servers, if any
49
- pluginRoots: () => [], // folders with skills and commands
50
- buildSubagents: () => undefined, // subagents, if it delegates to any
51
- };
52
-
53
- await ensureClaudeAuth(); // offers to create a Claude token if it finds none
54
- const config: BaseSessionConfig = { mode: "guided", projectDir: process.cwd() };
55
-
56
- // Each run gets a folder under .run/ (keep it out of git): its log, transcript and conversation.
57
- await runChatInk((run) => buildSessionOptions(config, run.dir, spec, { run }), {
58
- runsDir: path.resolve(".run"),
59
- header: { title: "My agent" },
60
- mode: config.mode, // Shift+Tab cycles through guided, step by step and plan
61
- fullscreen: true,
62
- });
63
- ```
64
-
65
- Run it with `npx tsx agent.ts`: a full-screen chat opens, in your system's language, and the agent asks before anything it shouldn't do alone. The [tutorial](https://falkenslab.github.io/agent-kit/docs/getting-started/first-agent) takes it from there.
66
-
67
- ## 🧪 A taste of the code
68
-
69
- Give it a memory and a folder of sources, with the kit's own extensions:
70
-
71
- ```ts
72
- import { knowledge, memory, sources } from "@falkenslab/agent-kit";
73
-
74
- const spec: AgentSpec<BaseSessionConfig> = {
75
- // …
76
- extensions: [sources({ dir: "sources" }), knowledge({ dir: "knowledge" }), memory({ dir: "memory" })],
77
- };
78
- ```
79
-
80
- Let it install extensions from your marketplace, and keep its data from leaving the computer without asking:
81
-
82
- ```ts
83
- const config: BaseSessionConfig = {
84
- mode: "guided",
85
- projectDir: process.cwd(),
86
- extensionDirs: { agent: path.join(os.homedir(), ".my-agent", "extensions") },
87
- officialMarketplace: "falkenslab/agent-extensions",
88
- remoteServers: "ask",
89
- };
90
- ```
91
-
92
- Make a tool always wait for the person's yes, on every call:
93
-
94
- ```ts
95
- const spec: AgentSpec<BaseSessionConfig> = {
96
- // …
97
- extensionGate: { confirmTools: { canva: ["create-design", "publish-design"] } },
98
- };
99
- ```
100
-
101
- ## 📸 See it at work
102
-
103
- [Captain Whiskers](./examples/captain-whiskers), the kit's example agent: a retired pirate cat who tells jokes in your language, with a crew of subagents (one looks for new jokes on the web, one rates them, one tells the time), a logbook and an extension of his own. He runs in the terminal, in a browser and as a Windows app.
104
-
105
- <div align="center">
106
-
107
- <img src="https://raw.githubusercontent.com/falkenslab/agent-kit/main/docs/assets/captain-whiskers.png" alt="Captain Whiskers in Windows Terminal: his logo with the mode and the agent-kit version at the top, his crew at work in the middle, a task list under the spinner, and the framed prompt and status bar at the bottom" width="760"/>
108
-
109
- </div>
110
-
111
- See [everything he can do](https://falkenslab.github.io/agent-kit/docs/examples/captain-whiskers#what-he-can-do), with screenshots. For a real one, [miyagi](https://github.com/falkenslab/miyagi) is an assistant for Moodle teachers: it grades, answers the forum, builds content and sums up how the class is going, asking before publishing anything students will see. Oversight, memory, safety and the chat come from the kit; miyagi brings what it knows about Moodle.
112
-
113
- ## 🧰 What's inside
114
-
115
- | Area | For example |
116
- | --- | --- |
117
- | 🎚️ Modes and gates | Autonomous, guided, step by step and plan; the guided gate for extensions' tools, the plan gate, the step gate |
118
- | ✋ Human in the loop | Approvals, choices, free text, files, manual steps, MCP elicitation forms, a response file for hosts without a keyboard |
119
- | 🧠 Memory | A knowledge base (an LLM wiki), sources kept as obtained, a memory of the person, runs resumed with `--continue` and `/resume` |
120
- | 🧩 Extensions | Claude Code plugins with MCP servers (local, npm, remote with OAuth), skills, commands and subagents; scopes, a lock, workspace trust, options, marketplaces and a catalog |
121
- | 🛡️ Security | A file scope gate, denied paths, subagent gates, a remote-servers gate, isolation from the runner's own Claude Code, redacted transcripts |
122
- | 🤖 Subagents | Declared types only, in the foreground, without Bash for the main agent |
123
- | 🖥️ Interfaces | A full-screen Ink chat, a plain readline chat, a progress view, a wizard, themes; a chat controller for desktop apps and web pages |
124
- | 🌐 Languages | English, Spanish, French and German, for the interface, the replies and the extensions' servers |
125
-
126
- Every export, with what it takes and what it gives, is in the [API reference](https://falkenslab.github.io/agent-kit/docs/api).
127
-
128
- ## 📚 Documentation
129
-
130
- - **[Getting started](https://falkenslab.github.io/agent-kit/docs/getting-started/installation)**: installation and a first agent, step by step.
131
- - **[Modes](https://falkenslab.github.io/agent-kit/docs/core-concepts/modes)** and **[human in the loop](https://falkenslab.github.io/agent-kit/docs/human-in-the-loop/overview)**: how much the agent does alone, and how it asks.
132
- - **[Extensions](https://falkenslab.github.io/agent-kit/docs/extensions/overview)**: installing, writing and publishing them.
133
- - **[Security](https://falkenslab.github.io/agent-kit/docs/security/overview)**: every guardrail, and what the kit doesn't protect.
134
- - **[The terminal chat](https://falkenslab.github.io/agent-kit/docs/terminal-ui/ink-chat)**: its keys, panels, options and themes.
135
- - **[Captain Whiskers](https://falkenslab.github.io/agent-kit/docs/examples/captain-whiskers)**: the example agent, piece by piece.
136
-
137
- ## 🛠️ Developing
138
-
139
- The design decisions, and the SDK behaviors checked by hand behind several safeguards, are in [`.minispec/`](./.minispec/README.md).
140
-
141
- | Command | What it does |
142
- | --- | --- |
143
- | `npm run build` | Builds the kit into `dist/` |
144
- | `npm run typecheck` | Checks the types without building |
145
- | `npm run lint` | Checks the code style |
146
- | `npm test` | Runs all the tests (one file: `npx tsx --test test/tui/promptErrors.test.ts`) |
147
- | `npm run docs:install` | Installs the documentation site's dependencies (once) |
148
- | `npm run docs:start` | Serves the documentation site locally, reloading on changes |
149
- | `npm run docs:build` | Builds the documentation site (fails on broken links) |
150
-
151
- To try changes to the kit in an agent without publishing them, point its `package.json` to a local clone (`"file:../agent-kit"`) and run `npm run build` after each change.
152
-
153
- Working on the kit with Claude Code goes through Falken's Lab's own skills (commit, verify, update-docs, release, sync-issues, and the house style for READMEs, sites and Markdown), from the [falkenslab/falkenslab-plugins](https://github.com/falkenslab/falkenslab-plugins) marketplace. Install them once, for all of Falken's Lab's repositories:
154
-
155
- ```bash
156
- claude plugin marketplace add falkenslab/falkenslab-plugins
157
- claude plugin install falkenslab-dev@falkenslab-plugins
158
- claude plugin install falkenslab-style@falkenslab-plugins
159
- ```
160
-
161
- <details>
162
- <summary>What each plugin brings, and what stays in this repository</summary>
163
-
164
- - 🔧 **falkenslab-dev**: `commit`, `verify`, `update-docs`, `release`, `sync-issues`, `upgrade-deps` and `add-tool`.
165
- - 🎨 **falkenslab-style**: `falkenslab-docs`, `falkenslab-site-style`, `falkenslab-terminal-screenshots` and `markdown-writing`.
166
-
167
- Inside Claude Code, the same with `/plugin marketplace add falkenslab/falkenslab-plugins` and `/plugin install falkenslab-dev@falkenslab-plugins`; `/plugin marketplace update falkenslab-plugins` brings their latest version. This repository keeps only the skills that are agent-kit's own, in `.claude/skills/` (`add-export`, the MiniSpec ones and `upgrade-sdk`), and what its release adds to the general one is in [`CLAUDE.md`](./CLAUDE.md#releasing).
168
-
169
- </details>
170
-
171
- <div align="center">
172
-
173
- Made with 💖 by Falken's Lab Team
174
-
175
- </div>
1
+ <div align="center">
2
+
3
+ <img src="https://raw.githubusercontent.com/falkenslab/agent-kit/main/docs/static/img/logo.svg" alt="agent-kit logo: three blue building blocks and a coral one being set in place" width="112"/>
4
+
5
+ # agent-kit
6
+
7
+ **Build an AI agent on Claude that asks before it acts, remembers, and chats like Claude Code.**<br/>
8
+ You write what your agent knows and does; the kit brings oversight, safety, memory and the interface.
9
+
10
+ [![npm](https://img.shields.io/npm/v/@falkenslab/agent-kit?color=cb3837&logo=npm)](https://www.npmjs.com/package/@falkenslab/agent-kit)
11
+ ![Node.js 20+](https://img.shields.io/badge/node-20%2B-339933?logo=node.js&logoColor=white)
12
+ ![Claude Agent SDK](https://img.shields.io/badge/Claude-Agent%20SDK-d97757?logo=anthropic&logoColor=white)
13
+ ![TypeScript](https://img.shields.io/badge/types-included-3178c6?logo=typescript&logoColor=white)
14
+ [![License: MIT](https://img.shields.io/badge/license-MIT-16a34a)](LICENSE)
15
+
16
+ **[📖 Documentation](https://falkenslab.github.io/agent-kit/)** · [📖 Documentación en español](https://falkenslab.github.io/agent-kit/es/)
17
+
18
+ </div>
19
+
20
+ agent-kit is a foundation for building AI agents on top of the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk). It isn't an agent itself: it solves once what every agent needs (levels of oversight, approvals, safe limits, memory across sessions, extensions, and a terminal chat as good as Claude Code's), so each agent writes only what belongs to its own domain: what it knows how to do, with which tools, and how it talks. A teacher's assistant for Moodle and a pirate cat who tells jokes are built the same way.
21
+
22
+ ## ✨ Why agent-kit
23
+
24
+ - 🎚️ **You choose how much it acts alone.** Autonomous, guided (it asks before what others will see or can't be undone), step by step, or a plan mode where it only reads and plans; switch among them in the same conversation.
25
+ - ✋ **It asks a person, and the kit makes sure.** Approvals, choices, a file, a sign-in by hand; an installed extension's tool that changes things waits for a yes whatever the model decided, and answers can last for the session or for good.
26
+ - 🧠 **It remembers.** A knowledge base that grows with every session, the original documents it learns from kept untouched, a memory of the person, and every conversation to pick up again with `--continue` or `/resume`.
27
+ - 🛡️ **It stays within limits.** Only its own folders, never protected files, subagents that can't slip past the main agent's limits, secrets hidden from the logs, remote servers allowed, asked about or denied.
28
+ - 🧩 **It grows with extensions.** Claude Code plugins with MCP servers, skills and commands, installed from a marketplace or a folder, with their own options, sign-in and a catalog in the chat; no Git needed.
29
+ - 🌐 **It speaks your language.** Its interface in English, Spanish, French or German, and it answers in that language unless you write in another.
30
+ - 🖥️ **A terminal chat like Claude Code's, or none at all.** Full screen, live markdown, folded tool calls, panels, history, themes; or the same agent inside a desktop app or a web page with its own interface.
31
+
32
+ ## 🚀 Quick start
33
+
34
+ You need [Node.js](https://nodejs.org) 20 or later and a Claude account. In a project with `"type": "module"` in its `package.json`:
35
+
36
+ ```bash
37
+ npm install @falkenslab/agent-kit tsx
38
+ ```
39
+
40
+ Save this as `agent.ts`: the only part that's your agent's is the spec at the top.
41
+
42
+ ```ts
43
+ import path from "node:path";
44
+ import { buildSessionOptions, ensureClaudeAuth, runChatInk, type AgentSpec, type BaseSessionConfig } from "@falkenslab/agent-kit";
45
+
46
+ const spec: AgentSpec<BaseSessionConfig> = {
47
+ buildSystemPrompt: () => "You are a pirate cat who tells jokes. Always answer in character.",
48
+ buildMcpServers: () => ({}), // its own MCP servers, if any
49
+ pluginRoots: () => [], // folders with skills and commands
50
+ buildSubagents: () => undefined, // subagents, if it delegates to any
51
+ };
52
+
53
+ await ensureClaudeAuth(); // offers to sign in with your Claude account, on Anthropic's page, if it isn't
54
+ const config: BaseSessionConfig = { mode: "guided", projectDir: process.cwd() };
55
+
56
+ // Each run gets a folder under .run/ (keep it out of git): its log, transcript and conversation.
57
+ await runChatInk((run) => buildSessionOptions(config, run.dir, spec, { run }), {
58
+ runsDir: path.resolve(".run"),
59
+ header: { title: "My agent" },
60
+ mode: config.mode, // Shift+Tab cycles through guided, step by step and plan
61
+ fullscreen: true,
62
+ });
63
+ ```
64
+
65
+ Run it with `npx tsx agent.ts`: a full-screen chat opens, in your system's language, and the agent asks before anything it shouldn't do alone. The [tutorial](https://falkenslab.github.io/agent-kit/docs/getting-started/first-agent) takes it from there.
66
+
67
+ ## 🧪 A taste of the code
68
+
69
+ Give it a memory and a folder of sources, with the kit's own extensions:
70
+
71
+ ```ts
72
+ import { knowledge, memory, sources } from "@falkenslab/agent-kit";
73
+
74
+ const spec: AgentSpec<BaseSessionConfig> = {
75
+ // …
76
+ extensions: [sources({ dir: "sources" }), knowledge({ dir: "knowledge" }), memory({ dir: "memory" })],
77
+ };
78
+ ```
79
+
80
+ Let it install extensions from your marketplace, and keep its data from leaving the computer without asking:
81
+
82
+ ```ts
83
+ const config: BaseSessionConfig = {
84
+ mode: "guided",
85
+ projectDir: process.cwd(),
86
+ extensionDirs: { agent: path.join(os.homedir(), ".my-agent", "extensions") },
87
+ officialMarketplace: "falkenslab/agent-extensions",
88
+ remoteServers: "ask",
89
+ };
90
+ ```
91
+
92
+ Make a tool always wait for the person's yes, on every call:
93
+
94
+ ```ts
95
+ const spec: AgentSpec<BaseSessionConfig> = {
96
+ // …
97
+ extensionGate: { confirmTools: { canva: ["create-design", "publish-design"] } },
98
+ };
99
+ ```
100
+
101
+ ## 📸 See it at work
102
+
103
+ [Captain Whiskers](./examples/captain-whiskers), the kit's example agent: a retired pirate cat who tells jokes in your language, with a crew of subagents (one looks for new jokes on the web, one rates them, one tells the time), a logbook and an extension of his own. He runs in the terminal, in a browser and as a Windows app.
104
+
105
+ <div align="center">
106
+
107
+ <img src="https://raw.githubusercontent.com/falkenslab/agent-kit/main/docs/assets/captain-whiskers.png" alt="Captain Whiskers in Windows Terminal: his logo with the mode and the agent-kit version at the top, his crew at work in the middle, a task list under the spinner, and the framed prompt and status bar at the bottom" width="760"/>
108
+
109
+ </div>
110
+
111
+ See [everything he can do](https://falkenslab.github.io/agent-kit/docs/examples/captain-whiskers#what-he-can-do), with screenshots. For a real one, [miyagi](https://github.com/falkenslab/miyagi) is an assistant for Moodle teachers: it grades, answers the forum, builds content and sums up how the class is going, asking before publishing anything students will see. Oversight, memory, safety and the chat come from the kit; miyagi brings what it knows about Moodle.
112
+
113
+ ## 🧰 What's inside
114
+
115
+ | Area | For example |
116
+ | --- | --- |
117
+ | 🎚️ Modes and gates | Autonomous, guided, step by step and plan; the guided gate for extensions' tools, the plan gate, the step gate |
118
+ | ✋ Human in the loop | Approvals, choices, free text, files, manual steps, MCP elicitation forms, a response file for hosts without a keyboard |
119
+ | 🧠 Memory | A knowledge base (an LLM wiki), sources kept as obtained, a memory of the person, runs resumed with `--continue` and `/resume` |
120
+ | 🧩 Extensions | Claude Code plugins with MCP servers (local, npm, remote with OAuth), skills, commands and subagents; scopes, a lock, workspace trust, options, marketplaces and a catalog |
121
+ | 🛡️ Security | A file scope gate, denied paths, subagent gates, a remote-servers gate, isolation from the runner's own Claude Code, redacted transcripts |
122
+ | 🤖 Subagents | Declared types only, in the foreground, without Bash for the main agent |
123
+ | 🖥️ Interfaces | A full-screen Ink chat, a plain readline chat, a progress view, a wizard, themes; a chat controller for desktop apps and web pages |
124
+ | 🌐 Languages | English, Spanish, French and German, for the interface, the replies and the extensions' servers |
125
+
126
+ Every export, with what it takes and what it gives, is in the [API reference](https://falkenslab.github.io/agent-kit/docs/api).
127
+
128
+ ## 📚 Documentation
129
+
130
+ - **[Getting started](https://falkenslab.github.io/agent-kit/docs/getting-started/installation)**: installation and a first agent, step by step.
131
+ - **[Modes](https://falkenslab.github.io/agent-kit/docs/core-concepts/modes)** and **[human in the loop](https://falkenslab.github.io/agent-kit/docs/human-in-the-loop/overview)**: how much the agent does alone, and how it asks.
132
+ - **[Extensions](https://falkenslab.github.io/agent-kit/docs/extensions/overview)**: installing, writing and publishing them.
133
+ - **[Security](https://falkenslab.github.io/agent-kit/docs/security/overview)**: every guardrail, and what the kit doesn't protect.
134
+ - **[The terminal chat](https://falkenslab.github.io/agent-kit/docs/terminal-ui/ink-chat)**: its keys, panels, options and themes.
135
+ - **[Captain Whiskers](https://falkenslab.github.io/agent-kit/docs/examples/captain-whiskers)**: the example agent, piece by piece.
136
+
137
+ ## 🛠️ Developing
138
+
139
+ The design decisions, and the SDK behaviors checked by hand behind several safeguards, are in [`.minispec/`](./.minispec/README.md).
140
+
141
+ | Command | What it does |
142
+ | --- | --- |
143
+ | `npm run build` | Builds the kit into `dist/` |
144
+ | `npm run typecheck` | Checks the types without building |
145
+ | `npm run lint` | Checks the code style |
146
+ | `npm test` | Runs all the tests (one file: `npx tsx --test test/tui/promptErrors.test.ts`) |
147
+ | `npm run docs:install` | Installs the documentation site's dependencies (once) |
148
+ | `npm run docs:start` | Serves the documentation site locally, reloading on changes |
149
+ | `npm run docs:build` | Builds the documentation site (fails on broken links) |
150
+
151
+ To try changes to the kit in an agent without publishing them, point its `package.json` to a local clone (`"file:../agent-kit"`) and run `npm run build` after each change.
152
+
153
+ Working on the kit with Claude Code goes through Falken's Lab's own skills (commit, verify, update-docs, release, sync-issues, and the house style for READMEs, sites and Markdown), from the [falkenslab/falkenslab-plugins](https://github.com/falkenslab/falkenslab-plugins) marketplace. Install them once, for all of Falken's Lab's repositories:
154
+
155
+ ```bash
156
+ claude plugin marketplace add falkenslab/falkenslab-plugins
157
+ claude plugin install falkenslab-dev@falkenslab-plugins
158
+ claude plugin install falkenslab-style@falkenslab-plugins
159
+ ```
160
+
161
+ <details>
162
+ <summary>What each plugin brings, and what stays in this repository</summary>
163
+
164
+ - 🔧 **falkenslab-dev**: `commit`, `verify`, `update-docs`, `release`, `sync-issues`, `upgrade-deps` and `add-tool`.
165
+ - 🎨 **falkenslab-style**: `falkenslab-docs`, `falkenslab-site-style`, `falkenslab-terminal-screenshots` and `markdown-writing`.
166
+
167
+ Inside Claude Code, the same with `/plugin marketplace add falkenslab/falkenslab-plugins` and `/plugin install falkenslab-dev@falkenslab-plugins`; `/plugin marketplace update falkenslab-plugins` brings their latest version. This repository keeps only the skills that are agent-kit's own, in `.claude/skills/` (`add-export`, the MiniSpec ones and `upgrade-sdk`), and what its release adds to the general one is in [`CLAUDE.md`](./CLAUDE.md#releasing).
168
+
169
+ </details>
170
+
171
+ <div align="center">
172
+
173
+ Made with 💖 by Falken's Lab Team
174
+
175
+ </div>
@@ -1,25 +1,57 @@
1
- /** What `resolveClaudeAuth()` and `ensureClaudeAuth()` accept besides the environment. */
2
- export interface ClaudeAuthConfig {
3
- /** A previously-resolved token the caller already has (from wherever it stores one, if
4
- * anywhere) — this module owns no persistence of its own, unlike an earlier version
5
- * that read/wrote `~/.<appName>/config.json` itself (see git history). Sourcing this
6
- * value across runs, if a concrete agent wants that at all, is entirely its own concern. */
7
- claudeCodeOAuthToken?: string;
1
+ /**
2
+ * The Claude Code binary the Agent SDK ships (`@anthropic-ai/claude-agent-sdk-<platform>-<arch>`),
3
+ * unmodified: the copy unpacked next to the archive when the agent runs packaged, or else the
4
+ * one resolved from `node_modules` as the SDK resolves it. `undefined` when it isn't installed.
5
+ * `resolve` and the platform are replaceable for tests.
6
+ */
7
+ export declare function claudeExecutable(resolve?: (request: string) => string, platform?: NodeJS.Platform, arch?: string): string | undefined;
8
+ /** What `claudeSignedIn()` and `startClaudeSignIn()` accept. */
9
+ export interface ClaudeAuthOptions {
10
+ /** The binary to run instead of the SDK's own (`claudeExecutable()`); a `.js`/`.mjs` file runs with Node, as the SDK runs one. */
11
+ executable?: string;
8
12
  }
13
+ /** The binary and its arguments as a process to spawn: a JavaScript file runs with this Node. */
14
+ export declare function claudeCommand(executable: string, args: string[]): {
15
+ file: string;
16
+ args: string[];
17
+ };
9
18
  /**
10
- * Pure lookup, no I/O and no console output of any kind — safe to call from a
11
- * headless/non-interactive consumer (a server, an Electron main process) as much as from a
12
- * terminal one.
19
+ * Whether the agent can reach Claude: true when the person's environment has `ANTHROPIC_API_KEY`
20
+ * or `CLAUDE_CODE_OAUTH_TOKEN` (their own setting); otherwise what the Claude Code binary says
21
+ * (`claude auth status --json`, its `loggedIn`). Never reads the credentials file, and prints
22
+ * nothing: safe from a server or an Electron main process. False when there is no binary.
23
+ */
24
+ export declare function claudeSignedIn(options?: ClaudeAuthOptions): Promise<boolean>;
25
+ /** A sign-in in progress (`startClaudeSignIn()`): Claude Code's own `auth login`, which keeps the credentials itself. */
26
+ export interface ClaudeSignIn {
27
+ /** Anthropic's sign-in page, to show as a link in case the browser didn't open; `undefined` if the binary ended without printing one. */
28
+ url: Promise<string | undefined>;
29
+ /** The code Anthropic's page shows after signing in, when it shows one: handed to the binary, which waits for it. */
30
+ sendCode(code: string): void;
31
+ /** Once the binary has ended: whether the person is signed in (`claudeSignedIn()`). */
32
+ done: Promise<boolean>;
33
+ /** Stops the sign-in; `done` then resolves with whether the person is signed in anyway. */
34
+ cancel(): void;
35
+ }
36
+ /**
37
+ * Signs the person in to their Claude account through Anthropic's own flow, for a host without a
38
+ * terminal (a web page, a desktop app): runs `claude auth login` (`console: true` for an
39
+ * Anthropic Console account, billed by API usage) with plain pipes. The binary opens the browser
40
+ * itself; the host shows `url` as a link in case it didn't, and a box for the code Anthropic's
41
+ * page shows (`sendCode`). The binary keeps the credentials: the kit never sees a token. Throws
42
+ * when the binary isn't installed.
13
43
  *
14
- * Checks, in order: `ANTHROPIC_API_KEY` (a complete authentication path on its own — API
15
- * billing instead of subscription, so nothing else is checked when it's set),
16
- * `CLAUDE_CODE_OAUTH_TOKEN`, then `config.claudeCodeOAuthToken` if the caller passed one.
17
- * Returns `true` and (for the `config` case) sets `CLAUDE_CODE_OAUTH_TOKEN` on the process
18
- * as a side effect once a token is found — the SDK's own subprocess reads that env var
19
- * directly, never anything this kit passes it explicitly; `false` when none of the three
20
- * has one. Getting a token in that case (e.g. by walking the user through
21
- * `claude setup-token`) is a terminal-UX concern, not this function's — see
22
- * `ensureClaudeAuth()` in tui/claudeAuth.ts, which calls this first and only falls back to
23
- * that interactive flow when it returns `false`.
44
+ * Confirmed empirically (Claude Code 2.1.310): without a TTY, `auth login` prints "If the browser
45
+ * didn't open, visit: <url>" (the URL may come wrapped in OSC 8 hyperlink escapes) and then waits
46
+ * at "Paste code here if prompted >".
47
+ */
48
+ export declare function startClaudeSignIn(options?: ClaudeAuthOptions & {
49
+ console?: boolean;
50
+ }): ClaudeSignIn;
51
+ /**
52
+ * The sign-in URL in what `claude auth login` printed so far: an OSC 8 hyperlink's target, or a
53
+ * URL in the text with its escapes removed, Anthropic's authorize page (`/oauth/authorize`)
54
+ * first. A URL in the text counts only once whitespace follows it (the rest may still be on its
55
+ * way), unless `complete` says the output is all there is.
24
56
  */
25
- export declare function resolveClaudeAuth(config?: ClaudeAuthConfig): boolean;
57
+ export declare function signInUrl(output: string, complete?: boolean): string | undefined;
@@ -1,27 +1,140 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { claudeBinaryRequests, unpackedClaudeExecutable } from "./packaged.js";
5
+ /*
6
+ * Signing in to Claude without ever touching a credential (ADR-009, #90). Anthropic's terms
7
+ * (https://code.claude.com/docs/en/legal-and-compliance, "Authentication and credential use")
8
+ * say an Agent SDK developer may not collect, store or intermediate Claude.ai credentials or
9
+ * session tokens: a Claude account signs in through Anthropic's own flow, in the unmodified
10
+ * Claude Code binary. The SDK ships that binary, so the kit runs its `auth status` and
11
+ * `auth login` and nothing else: the binary opens Anthropic's page and keeps the credentials
12
+ * itself (`~/.claude/.credentials.json`, the Keychain on macOS), where the SDK's sessions find
13
+ * them. The kit never reads, returns or stores a token. An API key (`ANTHROPIC_API_KEY`) is
14
+ * allowed, and so is a person's own `CLAUDE_CODE_OAUTH_TOKEN`: both are their setting, not
15
+ * something the kit collects.
16
+ */
1
17
  /**
2
- * Pure lookup, no I/O and no console output of any kind — safe to call from a
3
- * headless/non-interactive consumer (a server, an Electron main process) as much as from a
4
- * terminal one.
5
- *
6
- * Checks, in order: `ANTHROPIC_API_KEY` (a complete authentication path on its own — API
7
- * billing instead of subscription, so nothing else is checked when it's set),
8
- * `CLAUDE_CODE_OAUTH_TOKEN`, then `config.claudeCodeOAuthToken` if the caller passed one.
9
- * Returns `true` and (for the `config` case) sets `CLAUDE_CODE_OAUTH_TOKEN` on the process
10
- * as a side effect once a token is found — the SDK's own subprocess reads that env var
11
- * directly, never anything this kit passes it explicitly; `false` when none of the three
12
- * has one. Getting a token in that case (e.g. by walking the user through
13
- * `claude setup-token`) is a terminal-UX concern, not this function's — see
14
- * `ensureClaudeAuth()` in tui/claudeAuth.ts, which calls this first and only falls back to
15
- * that interactive flow when it returns `false`.
18
+ * The Claude Code binary the Agent SDK ships (`@anthropic-ai/claude-agent-sdk-<platform>-<arch>`),
19
+ * unmodified: the copy unpacked next to the archive when the agent runs packaged, or else the
20
+ * one resolved from `node_modules` as the SDK resolves it. `undefined` when it isn't installed.
21
+ * `resolve` and the platform are replaceable for tests.
16
22
  */
17
- export function resolveClaudeAuth(config = {}) {
18
- if (process.env.ANTHROPIC_API_KEY)
19
- return true;
20
- if (process.env.CLAUDE_CODE_OAUTH_TOKEN)
21
- return true;
22
- if (config.claudeCodeOAuthToken) {
23
- process.env.CLAUDE_CODE_OAUTH_TOKEN = config.claudeCodeOAuthToken;
23
+ export function claudeExecutable(resolve = createRequire(import.meta.url).resolve, platform = process.platform, arch = process.arch) {
24
+ const unpacked = unpackedClaudeExecutable(resolve, platform, arch);
25
+ if (unpacked)
26
+ return unpacked;
27
+ for (const request of claudeBinaryRequests(platform, arch)) {
28
+ try {
29
+ const file = resolve(request);
30
+ if (existsSync(file))
31
+ return file;
32
+ }
33
+ catch {
34
+ // That package isn't installed: the next one.
35
+ }
36
+ }
37
+ return undefined;
38
+ }
39
+ /** The binary and its arguments as a process to spawn: a JavaScript file runs with this Node. */
40
+ export function claudeCommand(executable, args) {
41
+ return /\.(c|m)?js$/i.test(executable) ? { file: process.execPath, args: [executable, ...args] } : { file: executable, args };
42
+ }
43
+ /**
44
+ * Whether the agent can reach Claude: true when the person's environment has `ANTHROPIC_API_KEY`
45
+ * or `CLAUDE_CODE_OAUTH_TOKEN` (their own setting); otherwise what the Claude Code binary says
46
+ * (`claude auth status --json`, its `loggedIn`). Never reads the credentials file, and prints
47
+ * nothing: safe from a server or an Electron main process. False when there is no binary.
48
+ */
49
+ export async function claudeSignedIn(options = {}) {
50
+ if (process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_CODE_OAUTH_TOKEN)
24
51
  return true;
52
+ const executable = options.executable ?? claudeExecutable();
53
+ if (!executable)
54
+ return false;
55
+ const command = claudeCommand(executable, ["auth", "status", "--json"]);
56
+ // Exit code 1 when signed out, with the same JSON (confirmed empirically): read it either way.
57
+ const output = await new Promise((resolve) => {
58
+ execFile(command.file, command.args, { timeout: 30_000, windowsHide: true }, (_error, stdout) => resolve(String(stdout ?? "")));
59
+ });
60
+ try {
61
+ return JSON.parse(output).loggedIn === true;
25
62
  }
26
- return false;
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /**
68
+ * Signs the person in to their Claude account through Anthropic's own flow, for a host without a
69
+ * terminal (a web page, a desktop app): runs `claude auth login` (`console: true` for an
70
+ * Anthropic Console account, billed by API usage) with plain pipes. The binary opens the browser
71
+ * itself; the host shows `url` as a link in case it didn't, and a box for the code Anthropic's
72
+ * page shows (`sendCode`). The binary keeps the credentials: the kit never sees a token. Throws
73
+ * when the binary isn't installed.
74
+ *
75
+ * Confirmed empirically (Claude Code 2.1.310): without a TTY, `auth login` prints "If the browser
76
+ * didn't open, visit: <url>" (the URL may come wrapped in OSC 8 hyperlink escapes) and then waits
77
+ * at "Paste code here if prompted >".
78
+ */
79
+ export function startClaudeSignIn(options = {}) {
80
+ const executable = options.executable ?? claudeExecutable();
81
+ if (!executable)
82
+ throw new Error("Claude Code's binary, which the Agent SDK ships (@anthropic-ai/claude-agent-sdk-<platform>-<arch>), isn't installed.");
83
+ const command = claudeCommand(executable, ["auth", "login", ...(options.console ? ["--console"] : [])]);
84
+ const child = spawn(command.file, command.args, { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
85
+ let output = "";
86
+ let resolveUrl;
87
+ const url = new Promise((resolve) => (resolveUrl = resolve));
88
+ const read = (chunk) => {
89
+ output += chunk.toString("utf8");
90
+ // Anthropic's page as soon as it's all there; any other URL only once the binary has ended.
91
+ const found = signInUrl(output);
92
+ if (found?.includes("/oauth/authorize"))
93
+ resolveUrl(found);
94
+ };
95
+ child.stdout?.on("data", read);
96
+ child.stderr?.on("data", read);
97
+ // A write after the binary ended fails on the stream: nothing to tell then.
98
+ child.stdin?.on("error", () => { });
99
+ const ended = new Promise((resolve) => {
100
+ child.on("error", () => resolve());
101
+ child.on("close", () => resolve());
102
+ });
103
+ const done = ended.then(() => {
104
+ resolveUrl(signInUrl(output, true));
105
+ return claudeSignedIn(options);
106
+ });
107
+ return {
108
+ url,
109
+ sendCode(code) {
110
+ if (child.stdin?.writable)
111
+ child.stdin.write(`${code.trim()}\n`);
112
+ },
113
+ done,
114
+ cancel() {
115
+ if (child.exitCode === null && child.signalCode === null)
116
+ child.kill();
117
+ },
118
+ };
119
+ }
120
+ // An OSC sequence (a hyperlink's `ESC ] 8 ; ; URL ESC \`, a title), ended by BEL or ST.
121
+ // eslint-disable-next-line no-control-regex -- ESC and BEL delimit terminal escapes, on purpose
122
+ const OSC = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
123
+ // A CSI sequence (colors, cursor moves).
124
+ // eslint-disable-next-line no-control-regex -- ESC starts the sequence
125
+ const CSI = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
126
+ // eslint-disable-next-line no-control-regex -- an OSC 8 hyperlink's target, between ESC/BEL delimiters
127
+ const HYPERLINK_TARGET = /\x1b\]8;[^;\x07\x1b]*;(https:\/\/[^\x07\x1b]+)(?:\x07|\x1b\\)/g;
128
+ /**
129
+ * The sign-in URL in what `claude auth login` printed so far: an OSC 8 hyperlink's target, or a
130
+ * URL in the text with its escapes removed, Anthropic's authorize page (`/oauth/authorize`)
131
+ * first. A URL in the text counts only once whitespace follows it (the rest may still be on its
132
+ * way), unless `complete` says the output is all there is.
133
+ */
134
+ export function signInUrl(output, complete = false) {
135
+ const targets = [...output.matchAll(HYPERLINK_TARGET)].map((match) => match[1]);
136
+ const text = output.replace(OSC, " ").replace(CSI, "");
137
+ const inText = [...text.matchAll(complete ? /https:\/\/\S+/g : /https:\/\/\S+(?=\s)/g)].map((match) => match[0]);
138
+ const urls = [...targets, ...inText];
139
+ return urls.find((url) => url.includes("/oauth/authorize")) ?? urls[0];
27
140
  }
@@ -9,8 +9,8 @@ export interface TranscriptLogger {
9
9
  /**
10
10
  * Logs every tool call (request + result) as one JSON line, redacting `secrets` (e.g. a
11
11
  * domain-specific password — see `BaseSessionConfig.secrets`) plus
12
- * `CLAUDE_CODE_OAUTH_TOKEN`, which this kit always scrubs regardless of domain since it
13
- * owns the auth-token concept (see claudeAuth.ts).
12
+ * `CLAUDE_CODE_OAUTH_TOKEN` when the person's environment sets one, which this kit always
13
+ * scrubs regardless of domain (it never handles a token itself: see claudeAuth.ts).
14
14
  */
15
15
  export declare function createTranscriptLogger(transcriptPath: string, secrets?: readonly string[]): TranscriptLogger;
16
16
  /** Avoids bloating the transcript with large payloads (browser-tool screenshots/base64). */
@@ -10,8 +10,8 @@ function redact(text, secrets) {
10
10
  /**
11
11
  * Logs every tool call (request + result) as one JSON line, redacting `secrets` (e.g. a
12
12
  * domain-specific password — see `BaseSessionConfig.secrets`) plus
13
- * `CLAUDE_CODE_OAUTH_TOKEN`, which this kit always scrubs regardless of domain since it
14
- * owns the auth-token concept (see claudeAuth.ts).
13
+ * `CLAUDE_CODE_OAUTH_TOKEN` when the person's environment sets one, which this kit always
14
+ * scrubs regardless of domain (it never handles a token itself: see claudeAuth.ts).
15
15
  */
16
16
  export function createTranscriptLogger(transcriptPath, secrets = []) {
17
17
  const allSecrets = [...secrets, process.env.CLAUDE_CODE_OAUTH_TOKEN].filter((s) => Boolean(s));
@@ -194,12 +194,14 @@ export const de = {
194
194
  presentingPlan: "Plan vorlegen",
195
195
  },
196
196
  auth: {
197
- noToken: "Es wurde kein Claude-Authentifizierungstoken gefunden",
198
- noTokenDetail: "(weder die Umgebungsvariable CLAUDE_CODE_OAUTH_TOKEN noch ein im Code übergebenes).",
199
- generateQuestion: 'Jetzt eines mit "claude setup-token" erzeugen? (erfordert ein Claude-Pro/Max-Abonnement)',
200
- cantStart: "Der Agent kann ohne Token nicht starten. Setze CLAUDE_CODE_OAUTH_TOKEN von Hand (oder starte erneut und lass es erzeugen) und versuche es noch einmal.",
201
- generatingHeading: "=== Claude-Authentifizierungstoken wird erzeugt ===",
202
- browserWillOpen: "Zum Anmelden öffnet sich ein Browser; folge den Anweisungen, die der Befehl unten ausgibt.",
203
- generated: "Token für diese Ausführung erzeugt. Speichere es selbst (z. B. CLAUDE_CODE_OAUTH_TOKEN im Profil deiner Shell), um diesen Schritt beim nächsten Mal zu überspringen.",
197
+ notSignedIn: "Du bist nicht bei Claude angemeldet",
198
+ notSignedInDetail: "Melde dich mit deinem Claude-Konto auf Anthropics eigener Seite an: Das übernimmt Claude Code, das mit diesem Agenten kommt, und es speichert die Anmeldung selbst. Oder setze ANTHROPIC_API_KEY, um einen API-Schlüssel zu verwenden.",
199
+ signInQuestion: "Jetzt mit deinem Claude-Konto anmelden?",
200
+ cantStart: "Der Agent kann ohne Claude nicht starten. Starte ihn erneut und melde dich an, oder setze ANTHROPIC_API_KEY.",
201
+ signingInHeading: "=== Anmeldung bei Claude ===",
202
+ browserWillOpen: "Ein Browser öffnet sich mit Anthropics Anmeldeseite. Zeigt die Seite einen Code, füge ihn hier ein.",
203
+ signedIn: "Angemeldet. Claude Code speichert die Anmeldung, du wirst nicht erneut gefragt.",
204
+ notCompleted: "Die Anmeldung wurde nicht abgeschlossen. Starte den Agenten erneut, um es noch einmal zu versuchen, oder setze ANTHROPIC_API_KEY.",
205
+ noClaude: "Das Claude-Code-Programm, das mit dem Agent SDK kommt (@anthropic-ai/claude-agent-sdk-<Plattform>-<Architektur>), ist nicht installiert, daher ist keine Anmeldung möglich. Installiere den Agenten neu, oder setze ANTHROPIC_API_KEY.",
204
206
  },
205
207
  };
@@ -203,13 +203,15 @@ export interface Messages {
203
203
  presentingPlan: string;
204
204
  };
205
205
  auth: {
206
- noToken: string;
207
- noTokenDetail: string;
208
- generateQuestion: string;
206
+ notSignedIn: string;
207
+ notSignedInDetail: string;
208
+ signInQuestion: string;
209
209
  cantStart: string;
210
- generatingHeading: string;
210
+ signingInHeading: string;
211
211
  browserWillOpen: string;
212
- generated: string;
212
+ signedIn: string;
213
+ notCompleted: string;
214
+ noClaude: string;
213
215
  };
214
216
  }
215
217
  export declare const en: Messages;
@@ -193,12 +193,14 @@ export const en = {
193
193
  presentingPlan: "Presenting the plan",
194
194
  },
195
195
  auth: {
196
- noToken: "No Claude authentication token was found",
197
- noTokenDetail: "(neither the CLAUDE_CODE_OAUTH_TOKEN environment variable, nor one passed in).",
198
- generateQuestion: 'Generate one now with "claude setup-token" (requires a Claude Pro/Max subscription)?',
199
- cantStart: "The agent can't start without a token. Set CLAUDE_CODE_OAUTH_TOKEN by hand (or run this again and accept generating it) and try again.",
200
- generatingHeading: "=== Generating a Claude authentication token ===",
201
- browserWillOpen: "A browser will open to log in; follow the instructions the command itself prints below.",
202
- generated: "Token generated for this run. Save it yourself (e.g. CLAUDE_CODE_OAUTH_TOKEN in your shell profile) to skip this next time.",
196
+ notSignedIn: "You aren't signed in to Claude",
197
+ notSignedInDetail: "Sign in with your Claude account on Anthropic's own page: Claude Code, which comes with this agent, does it and keeps the sign-in itself. Or set ANTHROPIC_API_KEY to use an API key.",
198
+ signInQuestion: "Sign in with your Claude account now?",
199
+ cantStart: "The agent can't start without Claude. Run it again and sign in, or set ANTHROPIC_API_KEY.",
200
+ signingInHeading: "=== Signing in to Claude ===",
201
+ browserWillOpen: "A browser will open on Anthropic's sign-in page. If the page shows a code, paste it here.",
202
+ signedIn: "Signed in. Claude Code keeps the sign-in, so this won't be asked again.",
203
+ notCompleted: "The sign-in didn't complete. Run the agent again to retry, or set ANTHROPIC_API_KEY.",
204
+ noClaude: "Claude Code's binary, which comes with the Agent SDK (@anthropic-ai/claude-agent-sdk-<platform>-<arch>), isn't installed, so there's no way to sign in. Reinstall the agent, or set ANTHROPIC_API_KEY.",
203
205
  },
204
206
  };
@@ -194,12 +194,14 @@ export const es = {
194
194
  presentingPlan: "Presentando el plan",
195
195
  },
196
196
  auth: {
197
- noToken: "No se ha encontrado ningún token de autenticación de Claude",
198
- noTokenDetail: "(ni la variable de entorno CLAUDE_CODE_OAUTH_TOKEN ni uno pasado por código).",
199
- generateQuestion: '¿Generar uno ahora con "claude setup-token"? (requiere una suscripción Claude Pro/Max)',
200
- cantStart: "El agente no puede arrancar sin un token. Define CLAUDE_CODE_OAUTH_TOKEN a mano (o vuelve a ejecutarlo y acepta generarlo) e inténtalo de nuevo.",
201
- generatingHeading: "=== Generando un token de autenticación de Claude ===",
202
- browserWillOpen: "Se abrirá un navegador para iniciar sesión; sigue las instrucciones que el propio comando muestra abajo.",
203
- generated: "Token generado para esta ejecución. Guárdalo tú (p. ej. CLAUDE_CODE_OAUTH_TOKEN en el perfil de tu shell) para no repetir esto la próxima vez.",
197
+ notSignedIn: "No has iniciado sesión en Claude",
198
+ notSignedInDetail: "Inicia sesión con tu cuenta de Claude en la propia página de Anthropic: lo hace Claude Code, que viene con este agente, y guarda él mismo la sesión. O define ANTHROPIC_API_KEY para usar una API key.",
199
+ signInQuestion: "¿Iniciar sesión ahora con tu cuenta de Claude?",
200
+ cantStart: "El agente no puede arrancar sin Claude. Vuelve a ejecutarlo e inicia sesión, o define ANTHROPIC_API_KEY.",
201
+ signingInHeading: "=== Iniciando sesión en Claude ===",
202
+ browserWillOpen: "Se abrirá un navegador en la página de inicio de sesión de Anthropic. Si la página muestra un código, pégalo aquí.",
203
+ signedIn: "Sesión iniciada. Claude Code la guarda, así que no se volverá a pedir.",
204
+ notCompleted: "El inicio de sesión no se completó. Vuelve a ejecutar el agente para reintentarlo, o define ANTHROPIC_API_KEY.",
205
+ noClaude: "No está instalado el binario de Claude Code que trae el Agent SDK (@anthropic-ai/claude-agent-sdk-<plataforma>-<arquitectura>), así que no hay cómo iniciar sesión. Reinstala el agente, o define ANTHROPIC_API_KEY.",
204
206
  },
205
207
  };
@@ -194,12 +194,14 @@ export const fr = {
194
194
  presentingPlan: "Présentation du plan",
195
195
  },
196
196
  auth: {
197
- noToken: "Aucun jeton d'authentification Claude n'a été trouvé",
198
- noTokenDetail: "(ni la variable d'environnement CLAUDE_CODE_OAUTH_TOKEN, ni un jeton passé par le code).",
199
- generateQuestion: 'En générer un maintenant avec "claude setup-token" ? (nécessite un abonnement Claude Pro/Max)',
200
- cantStart: "L'agent ne peut pas démarrer sans jeton. Définissez CLAUDE_CODE_OAUTH_TOKEN à la main (ou relancez et acceptez de le générer), puis réessayez.",
201
- generatingHeading: "=== Génération d'un jeton d'authentification Claude ===",
202
- browserWillOpen: "Un navigateur va s'ouvrir pour vous connecter ; suivez les instructions que la commande affiche ci-dessous.",
203
- generated: "Jeton généré pour cette exécution. Enregistrez-le vous-même (par ex. CLAUDE_CODE_OAUTH_TOKEN dans le profil de votre shell) pour éviter cette étape la prochaine fois.",
197
+ notSignedIn: "Tu n'es pas connecté à Claude",
198
+ notSignedInDetail: "Connecte-toi avec ton compte Claude sur la page d'Anthropic : c'est Claude Code, fourni avec cet agent, qui s'en charge et garde lui-même la connexion. Ou définis ANTHROPIC_API_KEY pour utiliser une clé d'API.",
199
+ signInQuestion: "Se connecter maintenant avec ton compte Claude ?",
200
+ cantStart: "L'agent ne peut pas démarrer sans Claude. Relance-le et connecte-toi, ou définis ANTHROPIC_API_KEY.",
201
+ signingInHeading: "=== Connexion à Claude ===",
202
+ browserWillOpen: "Un navigateur va s'ouvrir sur la page de connexion d'Anthropic. Si la page affiche un code, colle-le ici.",
203
+ signedIn: "Connecté. Claude Code garde la connexion : on ne te la redemandera pas.",
204
+ notCompleted: "La connexion n'a pas abouti. Relance l'agent pour réessayer, ou définis ANTHROPIC_API_KEY.",
205
+ noClaude: "Le binaire de Claude Code fourni avec l'Agent SDK (@anthropic-ai/claude-agent-sdk-<plateforme>-<architecture>) n'est pas installé : impossible de se connecter. Réinstalle l'agent, ou définis ANTHROPIC_API_KEY.",
204
206
  },
205
207
  };
@@ -1,5 +1,11 @@
1
1
  /** A path inside `…/app.asar/…`, as the unpacked copy next to it (`…/app.asar.unpacked/…`); any other path as it is. */
2
2
  export declare function outsideArchive(file: string): string;
3
+ /**
4
+ * The module requests that name the SDK's CLI binary on this platform, in the SDK's order:
5
+ * `@anthropic-ai/claude-agent-sdk-<platform>-<arch>/claude[.exe]`, both libcs on Linux (musl
6
+ * first when the system runs on it, as the SDK decides).
7
+ */
8
+ export declare function claudeBinaryRequests(platform?: NodeJS.Platform, arch?: string, musl?: boolean): string[];
3
9
  /**
4
10
  * The SDK's CLI binary unpacked next to the archive, when the kit runs packaged: the SDK would
5
11
  * resolve it inside `app.asar`, where it can't be executed, and its first turn would hang with
@@ -11,6 +11,22 @@ const ARCHIVE = /([\\/])app\.asar([\\/])/;
11
11
  export function outsideArchive(file) {
12
12
  return file.replace(ARCHIVE, "$1app.asar.unpacked$2");
13
13
  }
14
+ /**
15
+ * The module requests that name the SDK's CLI binary on this platform, in the SDK's order:
16
+ * `@anthropic-ai/claude-agent-sdk-<platform>-<arch>/claude[.exe]`, both libcs on Linux (musl
17
+ * first when the system runs on it, as the SDK decides).
18
+ */
19
+ export function claudeBinaryRequests(platform = process.platform, arch = process.arch, musl = platform === "linux" && runsOnMusl()) {
20
+ const packages = platform === "linux" ? (musl ? [`linux-${arch}-musl`, `linux-${arch}`] : [`linux-${arch}`, `linux-${arch}-musl`]) : [`${platform}-${arch}`];
21
+ return packages.map((suffix) => `@anthropic-ai/claude-agent-sdk-${suffix}/claude${platform === "win32" ? ".exe" : ""}`);
22
+ }
23
+ /** A Linux whose C library is musl (Alpine): Node's report names no glibc version there. */
24
+ function runsOnMusl() {
25
+ if (process.platform !== "linux")
26
+ return false;
27
+ const report = process.report?.getReport();
28
+ return Boolean(report?.header) && report?.header?.glibcVersionRuntime === undefined;
29
+ }
14
30
  /**
15
31
  * The SDK's CLI binary unpacked next to the archive, when the kit runs packaged: the SDK would
16
32
  * resolve it inside `app.asar`, where it can't be executed, and its first turn would hang with
@@ -19,11 +35,10 @@ export function outsideArchive(file) {
19
35
  * when not packaged, or when the build didn't unpack it: then the SDK finds its own.
20
36
  */
21
37
  export function unpackedClaudeExecutable(resolve = createRequire(import.meta.url).resolve, platform = process.platform, arch = process.arch) {
22
- const packages = platform === "linux" ? [`linux-${arch}`, `linux-${arch}-musl`] : [`${platform}-${arch}`];
23
- for (const suffix of packages) {
38
+ for (const request of claudeBinaryRequests(platform, arch)) {
24
39
  let file;
25
40
  try {
26
- file = resolve(`@anthropic-ai/claude-agent-sdk-${suffix}/claude${platform === "win32" ? ".exe" : ""}`);
41
+ file = resolve(request);
27
42
  }
28
43
  catch {
29
44
  continue;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { setTheme, getTheme, DEFAULT_THEME, type Theme, type ThemeColor } from "
3
3
  export { isExitPromptError } from "./tui/promptErrors.js";
4
4
  export { allowAnyMcpTool } from "./core/mcpPermissions.js";
5
5
  export { createPromptLoader } from "./core/promptTemplate.js";
6
- export { resolveClaudeAuth, type ClaudeAuthConfig } from "./core/claudeAuth.js";
6
+ export { claudeExecutable, claudeSignedIn, startClaudeSignIn, type ClaudeAuthOptions, type ClaudeSignIn } from "./core/claudeAuth.js";
7
7
  export { ensureClaudeAuth } from "./tui/claudeAuth.js";
8
8
  export { createFriendlyToolLabel, truncate, truncatePath, withToolLabels, withToolPhrases, withToolResults, quietResults, type ToolResult, type ToolDescriber, type ToolLabel, type ToolLabels, } from "./core/toolLabels.js";
9
9
  export type { Mode, BaseSessionConfig, AgentSpec, AgentIdentity, PlanModeSpec, ExtensionGateSpec } from "./core/agentSpec.js";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ export { setTheme, getTheme, DEFAULT_THEME } from "./tui/theme.js";
5
5
  export { isExitPromptError } from "./tui/promptErrors.js";
6
6
  export { allowAnyMcpTool } from "./core/mcpPermissions.js";
7
7
  export { createPromptLoader } from "./core/promptTemplate.js";
8
- export { resolveClaudeAuth } from "./core/claudeAuth.js";
8
+ export { claudeExecutable, claudeSignedIn, startClaudeSignIn } from "./core/claudeAuth.js";
9
9
  export { ensureClaudeAuth } from "./tui/claudeAuth.js";
10
10
  export { createFriendlyToolLabel, truncate, truncatePath, withToolLabels, withToolPhrases, withToolResults, quietResults, } from "./core/toolLabels.js";
11
11
  export { buildSessionOptions, createInputQueue, createDeferred, createModeControl, togglePlanMode } from "./core/session.js";
@@ -1,21 +1,16 @@
1
- import { type ClaudeAuthConfig } from "../core/claudeAuth.js";
2
1
  /**
3
- * The terminal-facing counterpart to `resolveClaudeAuth()` (core/claudeAuth.ts), which only
4
- * *looks up* a token. This one also gets you one: if `resolveClaudeAuth(config)` comes back
5
- * empty, it prints why, offers to run "claude setup-token" interactively, and sets the
6
- * result on `process.env.CLAUDE_CODE_OAUTH_TOKEN` for this run. Exits the process if the
7
- * user declines or cancels — there's no agent to run without a token.
2
+ * Makes sure the agent can reach Claude before a terminal chat starts: returns at once when
3
+ * `claudeSignedIn()` says so (an API key or the person's own token in the environment, or a
4
+ * Claude Code sign-in). Otherwise it says why, asks whether to sign in, and runs Claude Code's
5
+ * own `claude auth login` (the binary the Agent SDK ships) on this terminal: Anthropic's page in
6
+ * the browser, the code it shows pasted here. The binary keeps the credentials; nothing is
7
+ * returned or stored (ADR-009: Anthropic's terms forbid an agent collecting them). Exits the
8
+ * process when the person declines or the sign-in doesn't complete: there's no agent to run
9
+ * without Claude.
8
10
  *
9
- * Persisting a freshly-generated token across runs is *not* this function's job — unlike an
10
- * earlier version, this kit owns no config-file storage of its own (see git history). When
11
- * the interactive flow does generate one, it's returned so the caller can save it wherever
12
- * it wants (a file, an OS keychain, ...) and pass it back in as `config.claudeCodeOAuthToken`
13
- * next time; returns `undefined` when auth was already resolved (an env var, or the `config`
14
- * passed in) and nothing new was generated.
15
- *
16
- * Only makes sense with a real terminal in front of a human (a `runWizard()` confirm, colored console
17
- * output), which is why it lives here rather than in the non-interactive core.
11
+ * Only makes sense with a terminal in front of a person (a `runWizard()` confirm, colored
12
+ * output); a host without one uses `startClaudeSignIn()` from the core.
18
13
  */
19
- export declare function ensureClaudeAuth(config?: ClaudeAuthConfig, options?: {
14
+ export declare function ensureClaudeAuth(options?: {
20
15
  language?: string;
21
- }): Promise<string | undefined>;
16
+ }): Promise<void>;
@@ -1,78 +1,47 @@
1
1
  import { spawn } from "node:child_process";
2
- import { resolveClaudeAuth } from "../core/claudeAuth.js";
2
+ import { claudeCommand, claudeExecutable, claudeSignedIn } from "../core/claudeAuth.js";
3
3
  import { isExitPromptError } from "./promptErrors.js";
4
4
  import * as ui from "./ui.js";
5
5
  import { runWizard } from "./ink/wizard.js";
6
6
  import { applyLanguage } from "./language.js";
7
7
  import { t } from "../core/messages/index.js";
8
- // The real format of "claude setup-token" tokens ("sk-ant-oat01-...", seen in the
9
- // installed @anthropic-ai/claude-code binary) — used to extract the token from its output.
10
- const TOKEN_PATTERN = /sk-ant-oat\d{2}-[A-Za-z0-9_-]+/;
11
- /**
12
- * Launches "claude setup-token" as an interactive child process: it opens a browser and
13
- * asks the user to authenticate, with instructions it prints itself. stdin/stderr are
14
- * inherited as-is so the interaction (pasting the authorization code, etc.) is identical
15
- * to running the command by hand; stdout is duplicated live and also buffered to extract
16
- * the token it prints at the end ("Your OAuth token (...): sk-ant-oat01-...").
17
- */
18
- function runSetupToken() {
19
- return new Promise((resolve, reject) => {
20
- // On Windows npx is npx.cmd, which only runs through a shell. The command is passed as
21
- // one fixed string there (nothing user-supplied in it): an args array with
22
- // `shell: true` triggers Node's DEP0190 warning, since args are concatenated unescaped.
23
- const stdio = ["inherit", "pipe", "inherit"];
24
- const child = process.platform === "win32"
25
- ? spawn("npx @anthropic-ai/claude-code setup-token", { stdio, shell: true })
26
- : spawn("npx", ["@anthropic-ai/claude-code", "setup-token"], { stdio });
27
- let output = "";
28
- child.stdout.on("data", (chunk) => {
29
- const text = chunk.toString("utf-8");
30
- output += text;
31
- process.stdout.write(text);
32
- });
33
- child.on("error", reject);
34
- child.on("exit", (code) => {
35
- if (code !== 0) {
36
- reject(new Error(`"claude setup-token" exited with code ${code}.`));
37
- return;
38
- }
39
- const match = output.match(TOKEN_PATTERN);
40
- if (!match) {
41
- reject(new Error('"claude setup-token" exited without errors, but no token was found in its ' +
42
- "output (sk-ant-oat...). Copy it by hand into CLAUDE_CODE_OAUTH_TOKEN if you see it above."));
43
- return;
44
- }
45
- resolve(match[0]);
46
- });
8
+ /** Runs Claude Code's own `auth login` on this terminal: it opens the browser, and the person pastes the code here. */
9
+ function runLogin(executable) {
10
+ const command = claudeCommand(executable, ["auth", "login"]);
11
+ return new Promise((resolve) => {
12
+ const child = spawn(command.file, command.args, { stdio: "inherit" });
13
+ child.on("error", () => resolve());
14
+ child.on("close", () => resolve());
47
15
  });
48
16
  }
49
17
  /**
50
- * The terminal-facing counterpart to `resolveClaudeAuth()` (core/claudeAuth.ts), which only
51
- * *looks up* a token. This one also gets you one: if `resolveClaudeAuth(config)` comes back
52
- * empty, it prints why, offers to run "claude setup-token" interactively, and sets the
53
- * result on `process.env.CLAUDE_CODE_OAUTH_TOKEN` for this run. Exits the process if the
54
- * user declines or cancels — there's no agent to run without a token.
55
- *
56
- * Persisting a freshly-generated token across runs is *not* this function's job — unlike an
57
- * earlier version, this kit owns no config-file storage of its own (see git history). When
58
- * the interactive flow does generate one, it's returned so the caller can save it wherever
59
- * it wants (a file, an OS keychain, ...) and pass it back in as `config.claudeCodeOAuthToken`
60
- * next time; returns `undefined` when auth was already resolved (an env var, or the `config`
61
- * passed in) and nothing new was generated.
18
+ * Makes sure the agent can reach Claude before a terminal chat starts: returns at once when
19
+ * `claudeSignedIn()` says so (an API key or the person's own token in the environment, or a
20
+ * Claude Code sign-in). Otherwise it says why, asks whether to sign in, and runs Claude Code's
21
+ * own `claude auth login` (the binary the Agent SDK ships) on this terminal: Anthropic's page in
22
+ * the browser, the code it shows pasted here. The binary keeps the credentials; nothing is
23
+ * returned or stored (ADR-009: Anthropic's terms forbid an agent collecting them). Exits the
24
+ * process when the person declines or the sign-in doesn't complete: there's no agent to run
25
+ * without Claude.
62
26
  *
63
- * Only makes sense with a real terminal in front of a human (a `runWizard()` confirm, colored console
64
- * output), which is why it lives here rather than in the non-interactive core.
27
+ * Only makes sense with a terminal in front of a person (a `runWizard()` confirm, colored
28
+ * output); a host without one uses `startClaudeSignIn()` from the core.
65
29
  */
66
- export async function ensureClaudeAuth(config = {}, options = {}) {
67
- if (resolveClaudeAuth(config))
68
- return undefined;
30
+ export async function ensureClaudeAuth(options = {}) {
31
+ if (await claudeSignedIn())
32
+ return;
69
33
  applyLanguage(options.language);
70
34
  const texts = t().auth;
71
- console.log(ui.warn(`\n${texts.noToken}`));
72
- console.log(ui.dim(texts.noTokenDetail));
35
+ const executable = claudeExecutable();
36
+ if (!executable) {
37
+ console.log(ui.error(`\n${texts.noClaude}`));
38
+ process.exit(1);
39
+ }
40
+ console.log(ui.warn(`\n${texts.notSignedIn}`));
41
+ console.log(ui.dim(texts.notSignedInDetail));
73
42
  try {
74
- const { generate } = await runWizard([{ type: "confirm", name: "generate", message: texts.generateQuestion, default: true }]);
75
- if (!generate) {
43
+ const { signIn } = await runWizard([{ type: "confirm", name: "signIn", message: texts.signInQuestion, default: true }]);
44
+ if (!signIn) {
76
45
  console.log(ui.error(`\n${texts.cantStart}`));
77
46
  process.exit(0);
78
47
  }
@@ -82,10 +51,12 @@ export async function ensureClaudeAuth(config = {}, options = {}) {
82
51
  process.exit(0);
83
52
  throw error;
84
53
  }
85
- console.log(ui.heading(`\n${texts.generatingHeading}`));
54
+ console.log(ui.heading(`\n${texts.signingInHeading}`));
86
55
  console.log(ui.dim(`${texts.browserWillOpen}\n`));
87
- const token = await runSetupToken();
88
- process.env.CLAUDE_CODE_OAUTH_TOKEN = token;
89
- console.log(ui.success(`\n${texts.generated}\n`));
90
- return token;
56
+ await runLogin(executable);
57
+ if (!(await claudeSignedIn())) {
58
+ console.log(ui.error(`\n${texts.notCompleted}`));
59
+ process.exit(1);
60
+ }
61
+ console.log(ui.success(`\n${texts.signedIn}\n`));
91
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falkenslab/agent-kit",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "description": "Library for building an agent on top of the Claude Agent SDK",
5
5
  "author": "Francisco Vargas Ruiz",
6
6
  "license": "MIT",