@pencil-agent/nano-pencil 1.10.6 → 1.10.7

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.
Files changed (210) hide show
  1. package/dist/core/mcp/mcp-guidance.js +44 -44
  2. package/dist/core/model-registry.d.ts +2 -2
  3. package/dist/core/model-registry.js +5 -2
  4. package/dist/core/usage-tracker.d.ts +39 -0
  5. package/dist/core/usage-tracker.js +168 -0
  6. package/dist/extensions/agent-reach/index.d.ts +10 -0
  7. package/dist/extensions/agent-reach/index.js +216 -0
  8. package/dist/extensions/link-world/index.d.ts +1 -0
  9. package/dist/extensions/link-world/index.js +33 -1
  10. package/dist/main.js +9 -2
  11. package/dist/modes/interactive/components/footer.js +5 -5
  12. package/dist/modes/interactive/interactive-mode.js +10 -6
  13. package/dist/modes/interactive/theme/warm.json +81 -81
  14. package/dist/nanopencil-defaults.d.ts +105 -6
  15. package/dist/nanopencil-defaults.js +259 -15
  16. package/dist/packages/agent-core/agent-loop.d.ts +21 -0
  17. package/dist/packages/agent-core/agent-loop.js +308 -0
  18. package/dist/packages/agent-core/agent.d.ts +157 -0
  19. package/dist/packages/agent-core/agent.js +410 -0
  20. package/dist/packages/agent-core/index.d.ts +5 -0
  21. package/dist/packages/agent-core/index.js +9 -0
  22. package/dist/packages/agent-core/package.json +10 -0
  23. package/dist/packages/agent-core/proxy.d.ts +85 -0
  24. package/dist/packages/agent-core/proxy.js +268 -0
  25. package/dist/packages/agent-core/types.d.ts +178 -0
  26. package/dist/packages/agent-core/types.js +2 -0
  27. package/dist/packages/ai/api-registry.d.ts +20 -0
  28. package/dist/packages/ai/api-registry.js +44 -0
  29. package/dist/packages/ai/cli.d.ts +3 -0
  30. package/dist/packages/ai/cli.js +116 -0
  31. package/dist/packages/ai/env-api-keys.d.ts +9 -0
  32. package/dist/packages/ai/env-api-keys.js +100 -0
  33. package/dist/packages/ai/index.d.ts +22 -0
  34. package/dist/packages/ai/index.js +21 -0
  35. package/dist/packages/ai/models.d.ts +24 -0
  36. package/dist/packages/ai/models.generated.d.ts +13288 -0
  37. package/dist/packages/ai/models.generated.js +13094 -0
  38. package/dist/packages/ai/models.js +55 -0
  39. package/dist/packages/ai/package.json +10 -0
  40. package/dist/packages/ai/providers/amazon-bedrock.d.ts +15 -0
  41. package/dist/packages/ai/providers/amazon-bedrock.js +597 -0
  42. package/dist/packages/ai/providers/anthropic.d.ts +33 -0
  43. package/dist/packages/ai/providers/anthropic.js +729 -0
  44. package/dist/packages/ai/providers/azure-openai-responses.d.ts +15 -0
  45. package/dist/packages/ai/providers/azure-openai-responses.js +184 -0
  46. package/dist/packages/ai/providers/github-copilot-headers.d.ts +8 -0
  47. package/dist/packages/ai/providers/github-copilot-headers.js +29 -0
  48. package/dist/packages/ai/providers/google-gemini-cli.d.ts +74 -0
  49. package/dist/packages/ai/providers/google-gemini-cli.js +744 -0
  50. package/dist/packages/ai/providers/google-shared.d.ts +65 -0
  51. package/dist/packages/ai/providers/google-shared.js +306 -0
  52. package/dist/packages/ai/providers/google-vertex.d.ts +15 -0
  53. package/dist/packages/ai/providers/google-vertex.js +371 -0
  54. package/dist/packages/ai/providers/google.d.ts +13 -0
  55. package/dist/packages/ai/providers/google.js +352 -0
  56. package/dist/packages/ai/providers/openai-codex-responses.d.ts +9 -0
  57. package/dist/packages/ai/providers/openai-codex-responses.js +699 -0
  58. package/dist/packages/ai/providers/openai-completions.d.ts +15 -0
  59. package/dist/packages/ai/providers/openai-completions.js +727 -0
  60. package/dist/packages/ai/providers/openai-responses-shared.d.ts +17 -0
  61. package/dist/packages/ai/providers/openai-responses-shared.js +427 -0
  62. package/dist/packages/ai/providers/openai-responses.d.ts +13 -0
  63. package/dist/packages/ai/providers/openai-responses.js +198 -0
  64. package/dist/packages/ai/providers/register-builtins.d.ts +3 -0
  65. package/dist/packages/ai/providers/register-builtins.js +63 -0
  66. package/dist/packages/ai/providers/simple-options.d.ts +8 -0
  67. package/dist/packages/ai/providers/simple-options.js +35 -0
  68. package/dist/packages/ai/providers/transform-messages.d.ts +8 -0
  69. package/dist/packages/ai/providers/transform-messages.js +155 -0
  70. package/dist/packages/ai/stream.d.ts +9 -0
  71. package/dist/packages/ai/stream.js +28 -0
  72. package/dist/packages/ai/types.d.ts +281 -0
  73. package/dist/packages/ai/types.js +2 -0
  74. package/dist/packages/ai/utils/event-stream.d.ts +21 -0
  75. package/dist/packages/ai/utils/event-stream.js +81 -0
  76. package/dist/packages/ai/utils/http-proxy.d.ts +1 -0
  77. package/dist/packages/ai/utils/http-proxy.js +15 -0
  78. package/dist/packages/ai/utils/json-parse.d.ts +9 -0
  79. package/dist/packages/ai/utils/json-parse.js +29 -0
  80. package/dist/packages/ai/utils/oauth/anthropic.d.ts +17 -0
  81. package/dist/packages/ai/utils/oauth/anthropic.js +104 -0
  82. package/dist/packages/ai/utils/oauth/github-copilot.d.ts +30 -0
  83. package/dist/packages/ai/utils/oauth/github-copilot.js +281 -0
  84. package/dist/packages/ai/utils/oauth/google-antigravity.d.ts +26 -0
  85. package/dist/packages/ai/utils/oauth/google-antigravity.js +373 -0
  86. package/dist/packages/ai/utils/oauth/google-gemini-cli.d.ts +26 -0
  87. package/dist/packages/ai/utils/oauth/google-gemini-cli.js +478 -0
  88. package/dist/packages/ai/utils/oauth/index.d.ts +62 -0
  89. package/dist/packages/ai/utils/oauth/index.js +133 -0
  90. package/dist/packages/ai/utils/oauth/openai-codex.d.ts +34 -0
  91. package/dist/packages/ai/utils/oauth/openai-codex.js +380 -0
  92. package/dist/packages/ai/utils/oauth/pkce.d.ts +13 -0
  93. package/dist/packages/ai/utils/oauth/pkce.js +31 -0
  94. package/dist/packages/ai/utils/oauth/types.d.ts +47 -0
  95. package/dist/packages/ai/utils/oauth/types.js +2 -0
  96. package/dist/packages/ai/utils/overflow.d.ts +52 -0
  97. package/dist/packages/ai/utils/overflow.js +115 -0
  98. package/dist/packages/ai/utils/sanitize-unicode.d.ts +22 -0
  99. package/dist/packages/ai/utils/sanitize-unicode.js +26 -0
  100. package/dist/packages/ai/utils/typebox-helpers.d.ts +17 -0
  101. package/dist/packages/ai/utils/typebox-helpers.js +21 -0
  102. package/dist/packages/ai/utils/validation.d.ts +18 -0
  103. package/dist/packages/ai/utils/validation.js +72 -0
  104. package/dist/packages/nano-mem/cli.js +21 -0
  105. package/dist/packages/nano-mem/dedup.d.ts +17 -0
  106. package/dist/packages/nano-mem/dedup.js +84 -0
  107. package/dist/packages/nano-mem/engine.d.ts +9 -0
  108. package/dist/packages/nano-mem/engine.js +79 -8
  109. package/dist/packages/nano-mem/full-insights-html.js +191 -191
  110. package/dist/packages/nano-mem/i18n.js +83 -55
  111. package/dist/packages/nano-mem/update.d.ts +21 -1
  112. package/dist/packages/nano-mem/update.js +35 -2
  113. package/dist/packages/nanomem/cli.d.ts +7 -0
  114. package/dist/packages/nanomem/cli.js +89 -0
  115. package/dist/packages/nanomem/config.d.ts +45 -0
  116. package/dist/packages/nanomem/config.js +47 -0
  117. package/dist/packages/nanomem/consolidation.d.ts +12 -0
  118. package/dist/packages/nanomem/consolidation.js +110 -0
  119. package/dist/packages/nanomem/engine.d.ts +66 -0
  120. package/dist/packages/nanomem/engine.js +491 -0
  121. package/dist/packages/nanomem/eviction.d.ts +15 -0
  122. package/dist/packages/nanomem/eviction.js +21 -0
  123. package/dist/packages/nanomem/extension.d.ts +10 -0
  124. package/dist/packages/nanomem/extension.js +263 -0
  125. package/dist/packages/nanomem/extraction.d.ts +9 -0
  126. package/dist/packages/nanomem/extraction.js +135 -0
  127. package/dist/packages/nanomem/full-insights-html.d.ts +7 -0
  128. package/dist/packages/nanomem/full-insights-html.js +311 -0
  129. package/dist/packages/nanomem/full-insights.d.ts +20 -0
  130. package/dist/packages/nanomem/full-insights.js +326 -0
  131. package/dist/packages/nanomem/i18n.d.ts +49 -0
  132. package/dist/packages/nanomem/i18n.js +168 -0
  133. package/dist/packages/nanomem/index.d.ts +17 -0
  134. package/dist/packages/nanomem/index.js +13 -0
  135. package/dist/packages/nanomem/insights-html.d.ts +7 -0
  136. package/dist/packages/nanomem/insights-html.js +430 -0
  137. package/dist/packages/nanomem/linking.d.ts +10 -0
  138. package/dist/packages/nanomem/linking.js +39 -0
  139. package/dist/packages/nanomem/package.json +10 -0
  140. package/dist/packages/nanomem/privacy.d.ts +15 -0
  141. package/dist/packages/nanomem/privacy.js +51 -0
  142. package/dist/packages/nanomem/scoring.d.ts +24 -0
  143. package/dist/packages/nanomem/scoring.js +62 -0
  144. package/dist/packages/nanomem/store.d.ts +15 -0
  145. package/dist/packages/nanomem/store.js +67 -0
  146. package/dist/packages/nanomem/types.d.ts +190 -0
  147. package/dist/packages/nanomem/types.js +6 -0
  148. package/dist/packages/nanomem/update.d.ts +13 -0
  149. package/dist/packages/nanomem/update.js +125 -0
  150. package/dist/packages/nanosoul/extension.d.ts +15 -0
  151. package/dist/packages/nanosoul/extension.js +39 -0
  152. package/dist/packages/tui/autocomplete.d.ts +50 -0
  153. package/dist/packages/tui/autocomplete.js +596 -0
  154. package/dist/packages/tui/components/box.d.ts +22 -0
  155. package/dist/packages/tui/components/box.js +104 -0
  156. package/dist/packages/tui/components/cancellable-loader.d.ts +22 -0
  157. package/dist/packages/tui/components/cancellable-loader.js +35 -0
  158. package/dist/packages/tui/components/editor.d.ts +205 -0
  159. package/dist/packages/tui/components/editor.js +1679 -0
  160. package/dist/packages/tui/components/image.d.ts +28 -0
  161. package/dist/packages/tui/components/image.js +69 -0
  162. package/dist/packages/tui/components/input.d.ts +37 -0
  163. package/dist/packages/tui/components/input.js +433 -0
  164. package/dist/packages/tui/components/loader.d.ts +21 -0
  165. package/dist/packages/tui/components/loader.js +49 -0
  166. package/dist/packages/tui/components/markdown.d.ts +95 -0
  167. package/dist/packages/tui/components/markdown.js +629 -0
  168. package/dist/packages/tui/components/select-list.d.ts +32 -0
  169. package/dist/packages/tui/components/select-list.js +152 -0
  170. package/dist/packages/tui/components/settings-list.d.ts +50 -0
  171. package/dist/packages/tui/components/settings-list.js +185 -0
  172. package/dist/packages/tui/components/spacer.d.ts +12 -0
  173. package/dist/packages/tui/components/spacer.js +23 -0
  174. package/dist/packages/tui/components/text.d.ts +19 -0
  175. package/dist/packages/tui/components/text.js +89 -0
  176. package/dist/packages/tui/components/truncated-text.d.ts +13 -0
  177. package/dist/packages/tui/components/truncated-text.js +51 -0
  178. package/dist/packages/tui/editor-component.d.ts +39 -0
  179. package/dist/packages/tui/editor-component.js +2 -0
  180. package/dist/packages/tui/fuzzy.d.ts +16 -0
  181. package/dist/packages/tui/fuzzy.js +107 -0
  182. package/dist/packages/tui/index.d.ts +23 -0
  183. package/dist/packages/tui/index.js +32 -0
  184. package/dist/packages/tui/keybindings.d.ts +39 -0
  185. package/dist/packages/tui/keybindings.js +114 -0
  186. package/dist/packages/tui/keys.d.ts +160 -0
  187. package/dist/packages/tui/keys.js +959 -0
  188. package/dist/packages/tui/kill-ring.d.ts +28 -0
  189. package/dist/packages/tui/kill-ring.js +44 -0
  190. package/dist/packages/tui/package.json +10 -0
  191. package/dist/packages/tui/stdin-buffer.d.ts +48 -0
  192. package/dist/packages/tui/stdin-buffer.js +317 -0
  193. package/dist/packages/tui/terminal-image.d.ts +68 -0
  194. package/dist/packages/tui/terminal-image.js +288 -0
  195. package/dist/packages/tui/terminal.d.ts +78 -0
  196. package/dist/packages/tui/terminal.js +249 -0
  197. package/dist/packages/tui/tui.d.ts +210 -0
  198. package/dist/packages/tui/tui.js +955 -0
  199. package/dist/packages/tui/undo-stack.d.ts +17 -0
  200. package/dist/packages/tui/undo-stack.js +25 -0
  201. package/dist/packages/tui/utils.d.ts +78 -0
  202. package/dist/packages/tui/utils.js +806 -0
  203. package/docs/APIKEY_COMMAND.md +2 -0
  204. package/docs/APIKEY_FIX_SUMMARY.md +1 -1
  205. package/docs/ARK_CODING_PLAN.md +51 -0
  206. package/docs/CHANGELOG.md +9 -0
  207. package/docs/NANOMEM_READ_WRITE.md +352 -0
  208. package/docs/QIANFAN_CODING_PLAN.md +52 -0
  209. package/docs/RELEASE_GUIDE.md +129 -129
  210. package/package.json +2 -2
@@ -12,23 +12,23 @@ export const API_KEY_GUIDANCE = {
12
12
  serverName: "GitHub",
13
13
  required: false,
14
14
  envVar: "GITHUB_TOKEN",
15
- instructions: `GitHub Token 用于访问 GitHub 仓库、issues 和 PRs。
16
-
17
- **获取步骤:**
18
- 1. 访问 https://github.com/settings/tokens
19
- 2. 点击 "Generate new token" (classic)
20
- 3. 勾选权限:
21
- - ✅ repo (Full control of private repositories)
22
- - ✅ public_repo (Access public repositories)
23
- 4. 点击 "Generate token"
24
- 5. 复制 token (格式: ghp_xxxxxxxxxxxxxxxxxxxx)
25
- 6. 在 mcp.json 中配置:
26
- {
27
- "id": "github",
28
- "enabled": true,
29
- "env": {
30
- "GITHUB_TOKEN": "你的token"
31
- }
15
+ instructions: `GitHub Token 用于访问 GitHub 仓库、issues 和 PRs。
16
+
17
+ **获取步骤:**
18
+ 1. 访问 https://github.com/settings/tokens
19
+ 2. 点击 "Generate new token" (classic)
20
+ 3. 勾选权限:
21
+ - ✅ repo (Full control of private repositories)
22
+ - ✅ public_repo (Access public repositories)
23
+ 4. 点击 "Generate token"
24
+ 5. 复制 token (格式: ghp_xxxxxxxxxxxxxxxxxxxx)
25
+ 6. 在 mcp.json 中配置:
26
+ {
27
+ "id": "github",
28
+ "enabled": true,
29
+ "env": {
30
+ "GITHUB_TOKEN": "你的token"
31
+ }
32
32
  }`,
33
33
  getKeyUrl: "https://github.com/settings/tokens",
34
34
  freeTier: "✅ 完全免费",
@@ -39,20 +39,20 @@ export const API_KEY_GUIDANCE = {
39
39
  serverName: "Brave Search",
40
40
  required: false,
41
41
  envVar: "BRAVE_API_KEY",
42
- instructions: `Brave Search API Key 用于网页搜索功能。
43
-
44
- **获取步骤:**
45
- 1. 访问 https://api.search.brave.com/app/keys
46
- 2. 注册账号(或登录)
47
- 3. 点击 "Create API Key"
48
- 4. 复制 API Key (格式: BSxxxxx)
49
- 5. 在 mcp.json 中配置:
50
- {
51
- "id": "brave-search",
52
- "enabled": true,
53
- "env": {
54
- "BRAVE_API_KEY": "你的API key"
55
- }
42
+ instructions: `Brave Search API Key 用于网页搜索功能。
43
+
44
+ **获取步骤:**
45
+ 1. 访问 https://api.search.brave.com/app/keys
46
+ 2. 注册账号(或登录)
47
+ 3. 点击 "Create API Key"
48
+ 4. 复制 API Key (格式: BSxxxxx)
49
+ 5. 在 mcp.json 中配置:
50
+ {
51
+ "id": "brave-search",
52
+ "enabled": true,
53
+ "env": {
54
+ "BRAVE_API_KEY": "你的API key"
55
+ }
56
56
  }`,
57
57
  getKeyUrl: "https://api.search.brave.com/app/keys",
58
58
  freeTier: "✅ 免费额度: 每月 2000 次查询",
@@ -63,19 +63,19 @@ export const API_KEY_GUIDANCE = {
63
63
  serverName: "PostgreSQL",
64
64
  required: false,
65
65
  envVar: "POSTGRES_CONNECTION_STRING",
66
- instructions: `PostgreSQL 连接字符串用于连接本地数据库。
67
-
68
- **配置步骤:**
69
- 1. 确保已安装 PostgreSQL
70
- 2. 准备连接字符串,格式:
71
- postgresql://user:password@localhost:5432/dbname
72
- 3. 在 mcp.json 中配置:
73
- {
74
- "id": "postgres",
75
- "enabled": true,
76
- "env": {
77
- "POSTGRES_CONNECTION_STRING": "你的连接字符串"
78
- }
66
+ instructions: `PostgreSQL 连接字符串用于连接本地数据库。
67
+
68
+ **配置步骤:**
69
+ 1. 确保已安装 PostgreSQL
70
+ 2. 准备连接字符串,格式:
71
+ postgresql://user:password@localhost:5432/dbname
72
+ 3. 在 mcp.json 中配置:
73
+ {
74
+ "id": "postgres",
75
+ "enabled": true,
76
+ "env": {
77
+ "POSTGRES_CONNECTION_STRING": "你的连接字符串"
78
+ }
79
79
  }`,
80
80
  freeTier: "✅ 完全免费 (本地数据库)",
81
81
  alternative: "使用 SQLite (默认启用)",
@@ -12,8 +12,8 @@ export declare const clearApiKeyCache: typeof clearConfigValueCache;
12
12
  export interface ModelRegistryOptions {
13
13
  /** When true, only load models from models.json (no built-in providers). Used by NanoPencil. */
14
14
  useOnlyCustomModels?: boolean;
15
- /** Provider id for which apiKey is optional in models.json (key stored in auth.json later). Used by NanoPencil. */
16
- allowOptionalApiKeyForProvider?: string;
15
+ /** Provider id(s) for which apiKey is optional in models.json (key stored in auth.json later). Used by NanoPencil. */
16
+ allowOptionalApiKeyForProvider?: string | string[];
17
17
  }
18
18
  export declare class ModelRegistry {
19
19
  readonly authStorage: AuthStorage;
@@ -309,8 +309,11 @@ export class ModelRegistry {
309
309
  if (!providerConfig.baseUrl) {
310
310
  throw new Error(`Provider ${providerName}: "baseUrl" is required when defining custom models.`);
311
311
  }
312
- const apiKeyOptional = this.allowOptionalApiKeyForProvider !== undefined &&
313
- providerName === this.allowOptionalApiKeyForProvider;
312
+ const allowed = this.allowOptionalApiKeyForProvider !== undefined &&
313
+ (Array.isArray(this.allowOptionalApiKeyForProvider)
314
+ ? this.allowOptionalApiKeyForProvider.includes(providerName)
315
+ : providerName === this.allowOptionalApiKeyForProvider);
316
+ const apiKeyOptional = !!allowed;
314
317
  if (!apiKeyOptional && !providerConfig.apiKey) {
315
318
  throw new Error(`Provider ${providerName}: "apiKey" is required when defining custom models.`);
316
319
  }
@@ -0,0 +1,39 @@
1
+ import type { Usage } from "@pencil-agent/ai";
2
+ export interface UsageTotals {
3
+ input: number;
4
+ output: number;
5
+ cacheRead: number;
6
+ cacheWrite: number;
7
+ cost: number;
8
+ }
9
+ export interface UsageStats {
10
+ total: UsageTotals;
11
+ byDay: Record<string, UsageTotals>;
12
+ byMonth: Record<string, UsageTotals>;
13
+ }
14
+ /**
15
+ * Tracks token usage across all sessions and runs.
16
+ *
17
+ * Writes an append-only JSONL log (usage.jsonl) plus a small running total file
18
+ * (usage-total.json) for fast "global total" queries.
19
+ */
20
+ export declare class UsageTracker {
21
+ private agentDir;
22
+ private usageLogPath;
23
+ private totalPath;
24
+ constructor(agentDir: string);
25
+ /**
26
+ * Record usage for a single assistant response.
27
+ *
28
+ * Safe to call frequently; errors are swallowed so as not to affect the agent.
29
+ */
30
+ record(usage: Usage, timestampMs?: number): void;
31
+ /**
32
+ * Get aggregated usage statistics:
33
+ * - total: global totals across all time
34
+ * - byDay: grouped by YYYY-MM-DD
35
+ * - byMonth: grouped by YYYY-MM
36
+ */
37
+ getUsageStats(): UsageStats;
38
+ }
39
+ //# sourceMappingURL=usage-tracker.d.ts.map
@@ -0,0 +1,168 @@
1
+ import { mkdirSync, existsSync, readFileSync, writeFileSync, appendFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ function emptyTotals() {
4
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
5
+ }
6
+ function addInto(target, delta) {
7
+ target.input += delta.input;
8
+ target.output += delta.output;
9
+ target.cacheRead += delta.cacheRead;
10
+ target.cacheWrite += delta.cacheWrite;
11
+ target.cost += delta.cost;
12
+ }
13
+ /**
14
+ * Tracks token usage across all sessions and runs.
15
+ *
16
+ * Writes an append-only JSONL log (usage.jsonl) plus a small running total file
17
+ * (usage-total.json) for fast "global total" queries.
18
+ */
19
+ export class UsageTracker {
20
+ agentDir;
21
+ usageLogPath;
22
+ totalPath;
23
+ constructor(agentDir) {
24
+ this.agentDir = agentDir;
25
+ this.usageLogPath = join(agentDir, "usage.jsonl");
26
+ this.totalPath = join(agentDir, "usage-total.json");
27
+ }
28
+ /**
29
+ * Record usage for a single assistant response.
30
+ *
31
+ * Safe to call frequently; errors are swallowed so as not to affect the agent.
32
+ */
33
+ record(usage, timestampMs) {
34
+ try {
35
+ // Ensure base directory exists
36
+ if (!existsSync(this.agentDir)) {
37
+ mkdirSync(this.agentDir, { recursive: true });
38
+ }
39
+ const date = timestampMs ? new Date(timestampMs) : new Date();
40
+ const iso = date.toISOString();
41
+ const day = iso.slice(0, 10); // YYYY-MM-DD
42
+ const month = iso.slice(0, 7); // YYYY-MM
43
+ const delta = {
44
+ input: usage.input ?? 0,
45
+ output: usage.output ?? 0,
46
+ cacheRead: usage.cacheRead ?? 0,
47
+ cacheWrite: usage.cacheWrite ?? 0,
48
+ cost: usage.cost?.total ?? 0,
49
+ };
50
+ // Append one line to usage.jsonl
51
+ const logEntry = JSON.stringify({
52
+ date: day,
53
+ month,
54
+ input: delta.input,
55
+ output: delta.output,
56
+ cacheRead: delta.cacheRead,
57
+ cacheWrite: delta.cacheWrite,
58
+ cost: delta.cost,
59
+ });
60
+ appendFileSync(this.usageLogPath, logEntry + "\n");
61
+ // Update running total (best-effort)
62
+ let total = emptyTotals();
63
+ if (existsSync(this.totalPath)) {
64
+ try {
65
+ const raw = readFileSync(this.totalPath, "utf8");
66
+ const parsed = JSON.parse(raw);
67
+ total = {
68
+ input: parsed.input ?? 0,
69
+ output: parsed.output ?? 0,
70
+ cacheRead: parsed.cacheRead ?? 0,
71
+ cacheWrite: parsed.cacheWrite ?? 0,
72
+ cost: parsed.cost ?? 0,
73
+ };
74
+ }
75
+ catch {
76
+ // Ignore parse errors and start fresh
77
+ total = emptyTotals();
78
+ }
79
+ }
80
+ addInto(total, delta);
81
+ writeFileSync(this.totalPath, JSON.stringify(total));
82
+ }
83
+ catch {
84
+ // Swallow all errors - usage tracking must never break the agent
85
+ }
86
+ }
87
+ /**
88
+ * Get aggregated usage statistics:
89
+ * - total: global totals across all time
90
+ * - byDay: grouped by YYYY-MM-DD
91
+ * - byMonth: grouped by YYYY-MM
92
+ */
93
+ getUsageStats() {
94
+ const total = emptyTotals();
95
+ const byDay = Object.create(null);
96
+ const byMonth = Object.create(null);
97
+ // Load global total if available
98
+ if (existsSync(this.totalPath)) {
99
+ try {
100
+ const raw = readFileSync(this.totalPath, "utf8");
101
+ const parsed = JSON.parse(raw);
102
+ total.input = parsed.input ?? 0;
103
+ total.output = parsed.output ?? 0;
104
+ total.cacheRead = parsed.cacheRead ?? 0;
105
+ total.cacheWrite = parsed.cacheWrite ?? 0;
106
+ total.cost = parsed.cost ?? 0;
107
+ }
108
+ catch {
109
+ // Ignore and fall back to computing from log
110
+ total.input = 0;
111
+ total.output = 0;
112
+ total.cacheRead = 0;
113
+ total.cacheWrite = 0;
114
+ total.cost = 0;
115
+ }
116
+ }
117
+ if (existsSync(this.usageLogPath)) {
118
+ try {
119
+ const raw = readFileSync(this.usageLogPath, "utf8");
120
+ const lines = raw.split("\n");
121
+ for (const line of lines) {
122
+ const trimmed = line.trim();
123
+ if (!trimmed)
124
+ continue;
125
+ let entry;
126
+ try {
127
+ entry = JSON.parse(trimmed);
128
+ }
129
+ catch {
130
+ continue;
131
+ }
132
+ const day = typeof entry.date === "string" ? entry.date : undefined;
133
+ const month = typeof entry.month === "string" ? entry.month : undefined;
134
+ const delta = {
135
+ input: Number(entry.input) || 0,
136
+ output: Number(entry.output) || 0,
137
+ cacheRead: Number(entry.cacheRead) || 0,
138
+ cacheWrite: Number(entry.cacheWrite) || 0,
139
+ cost: Number(entry.cost) || 0,
140
+ };
141
+ // If total file was missing or corrupt, rebuild it from log
142
+ if (!existsSync(this.totalPath)) {
143
+ addInto(total, delta);
144
+ }
145
+ if (day) {
146
+ if (!byDay[day])
147
+ byDay[day] = emptyTotals();
148
+ addInto(byDay[day], delta);
149
+ }
150
+ if (month) {
151
+ if (!byMonth[month])
152
+ byMonth[month] = emptyTotals();
153
+ addInto(byMonth[month], delta);
154
+ }
155
+ }
156
+ // If we had to rebuild total, persist it for next time
157
+ if (!existsSync(this.totalPath)) {
158
+ writeFileSync(this.totalPath, JSON.stringify(total));
159
+ }
160
+ }
161
+ catch {
162
+ // Ignore log read errors
163
+ }
164
+ }
165
+ return { total, byDay, byMonth };
166
+ }
167
+ }
168
+ //# sourceMappingURL=usage-tracker.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Agent Reach extension - installs and configures Agent Reach
3
+ * Adds /link-world command to give the AI agent internet access capabilities
4
+ */
5
+ import type { ExtensionAPI } from "../../core/extensions/types.js";
6
+ /**
7
+ * Agent Reach extension factory (default export)
8
+ */
9
+ export default function agentReachExtension(pi: ExtensionAPI): Promise<void>;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Agent Reach extension - installs and configures Agent Reach
3
+ * Adds /link-world command to give the AI agent internet access capabilities
4
+ */
5
+ /**
6
+ * Agent Reach extension factory (default export)
7
+ */
8
+ export default async function agentReachExtension(pi) {
9
+ // Register the /link-world command
10
+ pi.registerCommand("link-world", {
11
+ description: "Install Agent Reach to give the AI agent full internet access (Twitter, YouTube, Bilibili, Reddit, GitHub, 小红书, 抖音, etc.)",
12
+ handler: async (_args, ctx) => {
13
+ await handleLinkWorld(ctx, pi);
14
+ },
15
+ });
16
+ }
17
+ ;
18
+ /**
19
+ * Handle the /link-world command
20
+ * Installs Agent Reach and configures it
21
+ */
22
+ async function handleLinkWorld(_ctx, pi) {
23
+ // Send initial message
24
+ pi.sendMessage({
25
+ customType: "agent-reach-install",
26
+ content: [
27
+ {
28
+ type: "text",
29
+ text: `🌐 **正在安装 Agent Reach...**
30
+
31
+ Agent Reach 是一个让 AI 代理具备互联网访问能力的工具,支持以下平台:
32
+ - **Twitter/X** - 搜索和发布推文
33
+ - **YouTube** - 视频信息获取
34
+ - **Bilibili** - 视频信息获取
35
+ - **Reddit** - 帖子读取
36
+ - **GitHub** - 代码仓库搜索
37
+ - **小红书** - 帖子搜索
38
+ - **抖音** - 视频解析
39
+ - **LinkedIn** - 职位信息
40
+ - **Boss直聘** - 职位搜索
41
+ - **Exa Search** - AI 网页搜索
42
+
43
+ 正在执行安装...`,
44
+ },
45
+ ],
46
+ display: true,
47
+ });
48
+ try {
49
+ // Step 1: Install Agent Reach via pip
50
+ pi.sendMessage({
51
+ customType: "agent-reach-step",
52
+ content: [{ type: "text", text: "\n📦 **步骤 1/3**: 安装 Agent Reach..." }],
53
+ display: true,
54
+ });
55
+ const installResult = await pi.exec("pip", ["install", "https://github.com/Panniantong/agent-reach/archive/main.zip"]);
56
+ if (installResult.code !== 0) {
57
+ throw new Error(`安装失败: ${installResult.stderr}`);
58
+ }
59
+ pi.sendMessage({
60
+ customType: "agent-reach-step",
61
+ content: [{ type: "text", text: "✅ Agent Reach 安装成功" }],
62
+ display: true,
63
+ });
64
+ // Step 2: Run agent-reach install
65
+ pi.sendMessage({
66
+ customType: "agent-reach-step",
67
+ content: [
68
+ {
69
+ type: "text",
70
+ text: "\n⚙️ **步骤 2/3**: 配置环境(自动检测并安装依赖)...",
71
+ },
72
+ ],
73
+ display: true,
74
+ });
75
+ const configResult = await pi.exec("agent-reach", ["install", "--env=auto"]);
76
+ if (configResult.code !== 0) {
77
+ // If auto-install fails, try safe mode
78
+ pi.sendMessage({
79
+ customType: "agent-reach-step",
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: "⚠️ 自动安装遇到问题,尝试安全模式(不会自动安装系统包)...",
84
+ },
85
+ ],
86
+ display: true,
87
+ });
88
+ const safeResult = await pi.exec("agent-reach", ["install", "--env=auto", "--safe"]);
89
+ if (safeResult.code !== 0) {
90
+ pi.sendMessage({
91
+ customType: "agent-reach-warning",
92
+ content: [
93
+ {
94
+ type: "text",
95
+ text: `⚠️ 配置过程中遇到一些问题:
96
+
97
+ \`\`\`
98
+ ${safeResult.stderr}
99
+ \`\`\`
100
+
101
+ 你可以稍后手动运行以下命令完成配置:
102
+ \`\`\`bash
103
+ agent-reach doctor
104
+ \`\`\`
105
+ `,
106
+ },
107
+ ],
108
+ display: true,
109
+ });
110
+ return;
111
+ }
112
+ }
113
+ pi.sendMessage({
114
+ customType: "agent-reach-step",
115
+ content: [{ type: "text", text: "✅ 环境配置完成" }],
116
+ display: true,
117
+ });
118
+ // Step 3: Run agent-reach doctor to show status
119
+ pi.sendMessage({
120
+ customType: "agent-reach-step",
121
+ content: [
122
+ { type: "text", text: "\n🏥 **步骤 3/3**: 检查各通道状态..." },
123
+ ],
124
+ display: true,
125
+ });
126
+ const doctorResult = await pi.exec("agent-reach", ["doctor"]);
127
+ // Show doctor results
128
+ pi.sendMessage({
129
+ customType: "agent-reach-result",
130
+ content: [
131
+ {
132
+ type: "text",
133
+ text: `📊 **通道状态报告:**
134
+
135
+ \`\`\`
136
+ ${doctorResult.stdout}
137
+ \`\`\`
138
+ `,
139
+ },
140
+ ],
141
+ display: true,
142
+ });
143
+ // Final message with next steps
144
+ pi.sendMessage({
145
+ customType: "agent-reach-complete",
146
+ content: [
147
+ {
148
+ type: "text",
149
+ text: `
150
+ 🎉 **Agent Reach 安装完成!**
151
+
152
+ **可用命令:**
153
+ - \`agent-reach doctor\` - 查看通道状态
154
+ - \`agent-reach watch\` - 快速健康检查
155
+ - \`agent-reach check-update\` - 检查更新
156
+
157
+ **配置额外功能(可选):**
158
+ 部分平台需要额外配置才能使用:
159
+
160
+ 🔑 **Twitter** - 需要 Cookie:
161
+ \`\`\`bash
162
+ agent-reach configure twitter-cookies "YOUR_COOKIE_STRING"
163
+ \`\`\`
164
+
165
+ 🔑 **小红书** - 需要 Docker + MCP 服务:
166
+ \`\`\`bash
167
+ docker run -d --name xiaohongshu-mcp -p 18060:18060 xpzouying/xiaohongshu-mcp
168
+ mcporter config add xiaohongshu http://localhost:18060/mcp
169
+ \`\`\`
170
+
171
+ 🔑 **抖音** - 需要安装 MCP 服务:
172
+ \`\`\`bash
173
+ pip install douyin-mcp-server
174
+ # 启动服务(详见 Agent Reach 文档)
175
+ \`\`\`
176
+
177
+ 🌐 **代理配置(如需要):**
178
+ \`\`\`bash
179
+ agent-reach configure proxy http://user:pass@ip:port
180
+ \`\`\`
181
+
182
+ 详细文档:https://github.com/Panniantong/agent-reach
183
+ `,
184
+ },
185
+ ],
186
+ display: true,
187
+ });
188
+ }
189
+ catch (error) {
190
+ pi.sendMessage({
191
+ customType: "agent-reach-error",
192
+ content: [
193
+ {
194
+ type: "text",
195
+ text: `❌ **安装失败**
196
+
197
+ 错误信息: ${error instanceof Error ? error.message : String(error)}
198
+
199
+ 请检查:
200
+ 1. 是否已安装 Python 和 pip
201
+ 2. 网络连接是否正常
202
+ 3. 是否有足够的权限
203
+
204
+ 你可以手动安装:
205
+ \`\`\`bash
206
+ pip install https://github.com/Panniantong/agent-reach/archive/main.zip
207
+ agent-reach install --env=auto
208
+ \`\`\`
209
+ `,
210
+ },
211
+ ],
212
+ display: true,
213
+ });
214
+ }
215
+ }
216
+ //# sourceMappingURL=index.js.map
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * link-world 扩展:执行 /link-world 时让 AI 读取同目录下的安装文档并按要求安装。
3
+ * 安装后自动提供 internet-search Skill。
3
4
  */
4
5
  import type { ExtensionAPI } from "../../core/extensions/types.js";
5
6
  export default function linkWorldExtension(pi: ExtensionAPI): void;
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * link-world 扩展:执行 /link-world 时让 AI 读取同目录下的安装文档并按要求安装。
3
+ * 安装后自动提供 internet-search Skill。
3
4
  */
4
- import { readFileSync } from "node:fs";
5
+ import { existsSync, readFileSync } from "node:fs";
5
6
  import { dirname, join } from "node:path";
6
7
  import { fileURLToPath } from "node:url";
8
+ import { execSync } from "child_process";
7
9
  import { Box, Container, Spacer, Text } from "@pencil-agent/tui";
8
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
11
  const DOC_PATH = join(__dirname, "linkworld.md");
12
+ const SKILL_PATH = join(__dirname, "skills", "internet-search.md");
10
13
  const LINK_WORLD_CUSTOM_TYPE = "link-world-install";
11
14
  function getInstallDoc() {
12
15
  try {
@@ -16,6 +19,19 @@ function getInstallDoc() {
16
19
  return "";
17
20
  }
18
21
  }
22
+ /**
23
+ * 检查 agent-reach 是否已安装
24
+ */
25
+ function isAgentReachInstalled() {
26
+ try {
27
+ // 检查 agent-reach CLI 是否可用
28
+ execSync("agent-reach --version", { encoding: "utf-8", stdio: "pipe" });
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
19
35
  export default function linkWorldExtension(pi) {
20
36
  /** TUI 仅显示简短提示,不展示完整安装文档内容 */
21
37
  pi.registerMessageRenderer(LINK_WORLD_CUSTOM_TYPE, (message, _options, theme) => {
@@ -29,6 +45,22 @@ export default function linkWorldExtension(pi) {
29
45
  container.addChild(box);
30
46
  return container;
31
47
  });
48
+ // 注册 resources_discover 事件:当 agent-reach 已安装时,提供 internet-search skill
49
+ pi.on("resources_discover", async (_event) => {
50
+ // 检查 agent-reach 是否已安装
51
+ if (!isAgentReachInstalled()) {
52
+ // 未安装,不提供 skill
53
+ return {};
54
+ }
55
+ // 检查 skill 文件是否存在
56
+ if (!existsSync(SKILL_PATH)) {
57
+ return {};
58
+ }
59
+ // 返回 skill 路径
60
+ return {
61
+ skillPaths: [SKILL_PATH],
62
+ };
63
+ });
32
64
  pi.registerCommand("link-world", {
33
65
  description: "安装 link-world,为 AI 提供互联网访问(Twitter、YouTube、Bilibili、小红书、抖音等)",
34
66
  handler: async (_args, _ctx) => {
package/dist/main.js CHANGED
@@ -30,7 +30,7 @@ import { allTools } from "./core/tools/index.js";
30
30
  import { runMigrations, showDeprecationWarnings } from "./migrations.js";
31
31
  import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
32
32
  import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
33
- import { ensureNanopencilCodingPlanAuth, ensureNanopencilDefaultConfig, NANOPENCIL_DEFAULT_PROVIDER, } from "./nanopencil-defaults.js";
33
+ import { ensureNanopencilCodingPlanAuth, ensureNanopencilDefaultConfig, NANOPENCIL_ARK_CODING_PROVIDER, NANOPENCIL_DEFAULT_PROVIDER, NANOPENCIL_QIANFAN_CODING_PROVIDER, } from "./nanopencil-defaults.js";
34
34
  import { getNanopencilDefaultExtensionPaths } from "./pencil-mem-integration.js";
35
35
  // Check if running in development mode (not production)
36
36
  const isDevelopment = process.env.NODE_ENV !== "production";
@@ -507,7 +507,14 @@ export async function main(args) {
507
507
  }
508
508
  }
509
509
  const modelRegistry = new ModelRegistry(authStorage, getModelsPath(), APP_NAME === "nanopencil"
510
- ? { useOnlyCustomModels: true, allowOptionalApiKeyForProvider: NANOPENCIL_DEFAULT_PROVIDER }
510
+ ? {
511
+ useOnlyCustomModels: true,
512
+ allowOptionalApiKeyForProvider: [
513
+ NANOPENCIL_DEFAULT_PROVIDER,
514
+ NANOPENCIL_QIANFAN_CODING_PROVIDER,
515
+ NANOPENCIL_ARK_CODING_PROVIDER,
516
+ ],
517
+ }
511
518
  : {});
512
519
  const defaultExtPaths = APP_NAME === "nanopencil" ? getNanopencilDefaultExtensionPaths() : [];
513
520
  const resourceLoader = new DefaultResourceLoader({