@cteno/sdk

中文文档
2.11.5发布于 2026/11/08MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 2337

Run Cteno agents from Node.js. Bundles a local Cteno daemon per platform; no desktop app or account needed.

原始使用文档
文档目录9 节
  1. Install
  2. One-shot query
  3. Multi-turn sessions
  4. Your own agent: prompt, client tools, structured output
  5. Hooks
  6. Raw method calls and stability
  7. The daemon and the connection
  8. Entry points
  9. License

@cteno/sdk

Run Cteno agents from Node.js. Create a session, send a prompt, stream the answer — or build your own agent on Cteno with your own system prompt and tools that run in your process.

npm install @cteno/sdk also installs the Cteno daemon for your platform (@cteno/sdk-<os>-<arch>, an optional dependency). CtenoSession.open() starts it in local mode on first use: no desktop app, no account, your own model key. Requires Node ≥ 20.

Install

npm install @cteno/sdk

Register a model once (BYOK; stored in the SDK daemon's own data directory):

import { CtenoSession } from '@cteno/sdk/core';

const cteno = await CtenoSession.open();
await cteno.configureModel({ baseUrl: 'https://api.openai.com/v1', model: 'gpt-4.1-mini', apiKey: process.env.OPENAI_API_KEY! });

baseUrl is used as given: include the API version (https://api.openai.com/v1, https://api.deepseek.com/anthropic/v1, https://generativelanguage.googleapis.com/v1beta); the SDK appends only the resource path. A base that is missing or repeats a version segment makes configureModel() throw with the full URL it would have requested.

See the repository README for platforms, permissions, the daemon's lifecycle and troubleshooting.

One-shot query

import { query } from '@cteno/sdk/core';

for await (const message of query({ prompt: 'Summarize the files in this folder', cwd: './src' })) {
  if (message.type === 'assistant' && (message.kind === undefined || message.kind === 'message')) {
    console.log(message.text);   // answer text only; structured frames carry `kind`
  }
}

query() and send() yield a discriminated union:

typeMeaning
userYour prompt, echoed once the daemon durably accepted it
assistantA model frame. text always prints; data holds the parsed envelope for structured frames (tool calls, task lifecycle)
statusSession went thinking/idle — progress UI, safe to ignore
resultTurn finished. Always last. ok: false when the turn failed (model 404/401/429, quota, timeout); error is the original error text, errorKind its class

Breaking out of the loop early aborts your local wait and releases the connection; the daemon may still be working, exactly like ctrl-C on a CLI. Neither a timeout nor an abort proves the daemon stopped.

Multi-turn sessions

import { CtenoSession } from '@cteno/sdk/core';

const session = await CtenoSession.open();
const id = await session.create({ cwd: './src', title: 'refactor', permissionMode: 'bypassPermissions' });

for await (const message of session.send({ sessionId: id, prompt: 'Start with the parser' })) {
  if (message.type === 'assistant') console.log(message.text);
}
const { ok, text } = await session.run({ sessionId: id, prompt: 'Now write tests' });

const history = await session.history(id);
const trace = await session.trace(id);
const profiles = await session.listProfiles();   // model profiles the daemon can run

await session.close(id);
session.dispose();
// ok is false when the model call failed; error then holds the provider's original text

Tools that are not read-only need approval. Without one the SDK denies them at once (the model is told why), so nothing hangs. Decide per call with canUseTool on send() / run() / query():

await session.run({ sessionId: id, prompt: 'Run the tests',
  canUseTool: req => req.toolName === 'Bash' ? { allow: true } : { allow: false, reason: 'no' } });

or set permissionMode on create(): acceptEdits (file edits allowed), plan (no mutations), bypassPermissions (everything runs — only when you control the prompt and working directory).

Your own agent: prompt, client tools, structured output

Needs daemon ≥ 2.10.10 (capability sdk-session-spec-v1).

const id = await session.create({
  model: 'my-profile',
  spec: {
    systemPrompt: 'You are a city guide. Answer with one recommendation.',
    tools: { allow: [] },                               // no built-in Cteno tools
    clientTools: [{
      name: 'search_places',
      description: 'Search places by keyword',
      inputSchema: { type: 'object', properties: { keyword: { type: 'string' } }, required: ['keyword'] },
      handler: async ({ keyword }) => mySearch(String(keyword)),   // runs in YOUR process
    }],
    outputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] },
    limits: { maxIterations: 10, maxToolCalls: 8, turnTimeoutMs: 60_000 },
  },
});
const result = await session.run({ sessionId: id, prompt: 'Somewhere quiet for coffee?' });
console.log(result.output);                              // parsed structured result

Keep the CtenoSession open while the session runs: it serves the client tools. After reconnecting, re-attach handlers with serveClientTools(id, tools).

Spec fieldNotes
systemPromptstring = replace (a short safety floor is kept); { mode: 'append', text } keeps Cteno's prompt
contextdatetime (default on), language, modelIdentity, skills, workspace, extra: string[]
tools.allow / denybuilt-in and MCP tools; prefix* patterns
clientTools≤ 64; name [A-Za-z0-9_-]; a throwing handler becomes a tool error the model can retry
terminalTools, requireTerminalToolthe turn ends on the first successful terminal call
outputSchemaadds final_result; run() returns its parsed input as output
limitscan only tighten daemon defaults
history≤ 200 user/assistant messages from your own store, context only
tenant{ tenantId, userId } — isolates workdir, memory and quota per end user

Tenant sessions are meant for untrusted end users: their built-in tools are capped, writes are confined to a per-user directory, and per-tenant admission is controlled with tenantPolicy() / tenantStatus().

Hooks

const unsubscribe = await session.hooks.on('PreToolUse', 'shell', call => {
  if (JSON.stringify(call.payload.tool_input ?? {}).includes('rm -rf')) return { decision: 'deny', reason: 'not here' };
});

Raw method calls and stability

Anything not wrapped is reachable by the daemon's own method name (the machine-id scope is added for you):

await session.call('get-session-trace', { sessionId: id });

Methods in STABLE_METHODS (@cteno/sdk/method-tiers) follow semver. Others are daemon internals: call() still works but warns once; pass { quiet: true } when the dependency is deliberate.

The daemon and the connection

CtenoSession.open() attaches to the SDK's own daemon (one per user and instance root, default ~/Library/Application Support/cteno-sdk, ~/.local/share/cteno-sdk, %LOCALAPPDATA%\cteno-sdk; override with CTENO_SDK_HOME or open({ daemon: { home } })) and starts it if needed. It is separate from a Cteno desktop app on the same machine and never connects to it unless you pass socketPath. The daemon keeps running after your process exits; stopDaemon() from @cteno/sdk/daemon stops it.

The connection is the daemon's owner socket — a mode 0600 Unix socket, or a per-root named pipe on Windows. Your OS user is the credential; there are no tokens.

Entry points

ImportWhat
@cteno/sdk/corequery, CtenoSession, agent spec, hooks
@cteno/sdkTransport interface, SdkError, minimal CtenoClient
@cteno/sdk/noderaw Unix-socket / named-pipe transport (explicit path)
@cteno/sdk/daemonensureDaemon, stopDaemon, instanceLayout: the bundled daemon
@cteno/sdk/discoverfind a Cteno app's daemon socket (never used by open())
@cteno/sdk/socketioadapter for an injected, already-authenticated Socket.IO client
@cteno/sdk/method-tiersstable/internal method classification

License

MIT. The daemon binaries in @cteno/sdk-<platform> are not MIT; see the LICENSE.daemon file inside that package.

版本与发布记录

@cteno/sdk 共发布 13 个版本,首次发布于 2026/10/29;过去 12 个月发布了 13 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.11.5latest2026/11/083 天前
2.11.42026/11/083 天前
2.11.32026/11/073 天前
2.11.12026/11/056 天前
2.11.02026/11/047 天前
2.10.222026/11/047 天前

关于 @cteno/sdk 的常见问题

@cteno/sdk 是什么?

Run Cteno agents from Node.js. Bundles a local Cteno daemon per platform; no desktop app or account needed.

如何安装 @cteno/sdk?

在项目目录运行 npm install @cteno/sdk,或使用 pnpm add / yarn add / bun add。

@cteno/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cteno/sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@cteno/sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cteno/sdk 可以商用吗?

@cteno/sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cteno/sdk 的下载量有多少?

根据 npm 官方下载统计,@cteno/sdk 在 2026/10/11 – 2026/11/09 这 30 天内被下载 2,337 次,日均约 78 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cteno/sdk 还在维护吗?

最新版本 2.11.5 发布于 2026/11/08(3 天前);至今共发布 13 个版本,其中过去 12 个月发布了 13 个;首次发布于 2026/10/29。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。