@botconnector/bccli 0.4.16 → 0.5.0-rc.4

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 (185) hide show
  1. package/LICENSE +203 -21
  2. package/THIRD_PARTY_NOTICES.md +9 -0
  3. package/branding.mjs +347 -0
  4. package/cli.mjs +120 -0
  5. package/core.mjs +260 -0
  6. package/credentials.mjs +52 -0
  7. package/gateway.mjs +2 -0
  8. package/model-command.mjs +61 -0
  9. package/package.json +35 -59
  10. package/provider-cli.mjs +77 -0
  11. package/providers.mjs +166 -0
  12. package/proxy.mjs +242 -0
  13. package/startup-menu.mjs +120 -0
  14. package/README.en.md +0 -253
  15. package/README.md +0 -250
  16. package/dist/abort.d.ts +0 -5
  17. package/dist/acp-JZW3XHE5.js +0 -221
  18. package/dist/acp.d.ts +0 -44
  19. package/dist/agent.d.ts +0 -147
  20. package/dist/agents.d.ts +0 -4
  21. package/dist/args-XVO7TB4C.js +0 -13
  22. package/dist/args.d.ts +0 -59
  23. package/dist/budget.d.ts +0 -26
  24. package/dist/checkpoints.d.ts +0 -35
  25. package/dist/chunk-3CYQYHPK.js +0 -202
  26. package/dist/chunk-6JK4IBGK.js +0 -1075
  27. package/dist/chunk-6SDRWEPN.js +0 -50
  28. package/dist/chunk-7GM6NN5E.js +0 -341
  29. package/dist/chunk-7XD47KN5.js +0 -71
  30. package/dist/chunk-INMSTUB2.js +0 -56
  31. package/dist/chunk-ISOKH5WB.js +0 -256
  32. package/dist/chunk-JUC3ZOCV.js +0 -50
  33. package/dist/chunk-KDVL2NP6.js +0 -861
  34. package/dist/chunk-KEKDRROL.js +0 -216
  35. package/dist/chunk-KJIXA6MP.js +0 -159
  36. package/dist/chunk-OK2USAXX.js +0 -88
  37. package/dist/chunk-PSO7R5AD.js +0 -10
  38. package/dist/chunk-Q42ZGVAQ.js +0 -91
  39. package/dist/chunk-R754XIBJ.js +0 -523
  40. package/dist/chunk-S3QEZDKB.js +0 -270
  41. package/dist/chunk-SWNRA3EW.js +0 -78
  42. package/dist/chunk-TLCL6DC7.js +0 -378
  43. package/dist/chunk-V5D6PJQN.js +0 -8
  44. package/dist/chunk-YYTFJUO2.js +0 -148
  45. package/dist/chunk-ZJRZTMJF.js +0 -2669
  46. package/dist/cli.d.ts +0 -1
  47. package/dist/cli.js +0 -233
  48. package/dist/clipboardImage.d.ts +0 -17
  49. package/dist/commands.d.ts +0 -16
  50. package/dist/completion-Q7VHOPPV.js +0 -125
  51. package/dist/completion.d.ts +0 -14
  52. package/dist/config-T7MKGJVG.js +0 -34
  53. package/dist/config-YYEAEN2C.js +0 -25
  54. package/dist/config.d.ts +0 -99
  55. package/dist/context.d.ts +0 -13
  56. package/dist/contextWindow.d.ts +0 -3
  57. package/dist/diff.d.ts +0 -7
  58. package/dist/exit.d.ts +0 -9
  59. package/dist/extensions.d.ts +0 -34
  60. package/dist/ghBotCli-7VUGI63S.js +0 -680
  61. package/dist/ghBotCli.d.ts +0 -9
  62. package/dist/ghBotDispatch.d.ts +0 -68
  63. package/dist/ghBotRunner.d.ts +0 -82
  64. package/dist/ghBotServe.d.ts +0 -21
  65. package/dist/ghBotWebhook.d.ts +0 -45
  66. package/dist/hooks.d.ts +0 -27
  67. package/dist/i18n/id.d.ts +0 -1
  68. package/dist/i18n.d.ts +0 -16
  69. package/dist/integrationCli-FBRSTF5F.js +0 -876
  70. package/dist/integrationCli.d.ts +0 -9
  71. package/dist/keybinds.d.ts +0 -22
  72. package/dist/login-BSMUGW7L.js +0 -18
  73. package/dist/login.d.ts +0 -21
  74. package/dist/lsp.d.ts +0 -73
  75. package/dist/mcp/catalog.d.ts +0 -14
  76. package/dist/mcp/config.d.ts +0 -36
  77. package/dist/mcp/manager.d.ts +0 -52
  78. package/dist/mcp/oauth.d.ts +0 -82
  79. package/dist/mcpCli-ZT7CRYMP.js +0 -113
  80. package/dist/mcpCli.d.ts +0 -3
  81. package/dist/mcpServer/serve.d.ts +0 -8
  82. package/dist/mcpServer/server.d.ts +0 -19
  83. package/dist/models.d.ts +0 -13
  84. package/dist/modelsCli-2ZRW4CC4.js +0 -36
  85. package/dist/modelsCli.d.ts +0 -15
  86. package/dist/npmRunner-LU43GM3W.js +0 -46
  87. package/dist/npmRunner.d.ts +0 -21
  88. package/dist/oauthLogin-THSM6FE7.js +0 -210
  89. package/dist/oauthLogin.d.ts +0 -47
  90. package/dist/permissions.d.ts +0 -30
  91. package/dist/pluginSpec.d.ts +0 -25
  92. package/dist/plugins.d.ts +0 -45
  93. package/dist/pluginsCli-EKQI4CJW.js +0 -288
  94. package/dist/pluginsCli.d.ts +0 -16
  95. package/dist/prCli-FMQTOBIV.js +0 -94
  96. package/dist/prCli.d.ts +0 -46
  97. package/dist/presets.d.ts +0 -8
  98. package/dist/print.d.ts +0 -17
  99. package/dist/provider.d.ts +0 -145
  100. package/dist/providerCli-3TFEKV4C.js +0 -15
  101. package/dist/providerCli.d.ts +0 -17
  102. package/dist/providers.d.ts +0 -6
  103. package/dist/reasoning.d.ts +0 -12
  104. package/dist/repomap.d.ts +0 -10
  105. package/dist/sdk.d.ts +0 -61
  106. package/dist/sdk.js +0 -6494
  107. package/dist/serve-3BODPEPR.js +0 -532
  108. package/dist/serve-V6YVZTNR.js +0 -178
  109. package/dist/serve.d.ts +0 -37
  110. package/dist/serveHeartbeat.d.ts +0 -14
  111. package/dist/serveProtocol.d.ts +0 -47
  112. package/dist/session-LX4UNPCD.js +0 -12
  113. package/dist/session.d.ts +0 -33
  114. package/dist/setup-MFG3BWEU.js +0 -24
  115. package/dist/setup.d.ts +0 -79
  116. package/dist/slash/copy.d.ts +0 -1
  117. package/dist/slash/diff.d.ts +0 -1
  118. package/dist/slash/doctor.d.ts +0 -18
  119. package/dist/slash/export.d.ts +0 -2
  120. package/dist/slash/info.d.ts +0 -46
  121. package/dist/slash/memory.d.ts +0 -10
  122. package/dist/statsCli-7MIQHLVW.js +0 -353
  123. package/dist/statsCli.d.ts +0 -62
  124. package/dist/textToolCalls.d.ts +0 -6
  125. package/dist/thinking.d.ts +0 -17
  126. package/dist/tokenBudget.d.ts +0 -22
  127. package/dist/tools/bash.d.ts +0 -54
  128. package/dist/tools/diagnostics.d.ts +0 -13
  129. package/dist/tools/edit.d.ts +0 -12
  130. package/dist/tools/fetch.d.ts +0 -41
  131. package/dist/tools/glob.d.ts +0 -5
  132. package/dist/tools/globSafety.d.ts +0 -7
  133. package/dist/tools/grep.d.ts +0 -25
  134. package/dist/tools/ignore.d.ts +0 -2
  135. package/dist/tools/index.d.ts +0 -12
  136. package/dist/tools/memory.d.ts +0 -13
  137. package/dist/tools/paths.d.ts +0 -4
  138. package/dist/tools/plan.d.ts +0 -10
  139. package/dist/tools/read.d.ts +0 -16
  140. package/dist/tools/repomap.d.ts +0 -2
  141. package/dist/tools/sandbox.d.ts +0 -15
  142. package/dist/tools/shell.d.ts +0 -18
  143. package/dist/tools/skill.d.ts +0 -3
  144. package/dist/tools/task.d.ts +0 -28
  145. package/dist/tools/todo.d.ts +0 -13
  146. package/dist/tools/types.d.ts +0 -52
  147. package/dist/tools/websearch.d.ts +0 -9
  148. package/dist/tools/write.d.ts +0 -5
  149. package/dist/transferCli-SRSYQLSJ.js +0 -73
  150. package/dist/transferCli.d.ts +0 -23
  151. package/dist/ui/App.d.ts +0 -7
  152. package/dist/ui/DiffView.d.ts +0 -3
  153. package/dist/ui/EditableText.d.ts +0 -7
  154. package/dist/ui/LinePrompt.d.ts +0 -6
  155. package/dist/ui/ListPicker.d.ts +0 -11
  156. package/dist/ui/Markdown.d.ts +0 -8
  157. package/dist/ui/McpMenu.d.ts +0 -11
  158. package/dist/ui/ModelPicker.d.ts +0 -6
  159. package/dist/ui/PermissionPrompt.d.ts +0 -5
  160. package/dist/ui/PlanApproval.d.ts +0 -5
  161. package/dist/ui/PromptInput.d.ts +0 -20
  162. package/dist/ui/ProviderMenu.d.ts +0 -10
  163. package/dist/ui/Spinner.d.ts +0 -4
  164. package/dist/ui/StatusBar.d.ts +0 -9
  165. package/dist/ui/TodoList.d.ts +0 -4
  166. package/dist/ui/ToolBlock.d.ts +0 -14
  167. package/dist/ui/complete.d.ts +0 -8
  168. package/dist/ui/index.d.ts +0 -7
  169. package/dist/ui/lineEdit.d.ts +0 -42
  170. package/dist/ui/liveCap.d.ts +0 -9
  171. package/dist/ui/replay.d.ts +0 -4
  172. package/dist/ui/stdinFilter.d.ts +0 -18
  173. package/dist/ui/table.d.ts +0 -20
  174. package/dist/ui/theme.d.ts +0 -5
  175. package/dist/ui/thinkingMouse.d.ts +0 -38
  176. package/dist/ui/transcript.d.ts +0 -55
  177. package/dist/ui/tty.d.ts +0 -11
  178. package/dist/ui/vim.d.ts +0 -26
  179. package/dist/ui-LZ4JGF2V.js +0 -2913
  180. package/dist/update-P7MU4ZM7.js +0 -145
  181. package/dist/update.d.ts +0 -30
  182. package/dist/verify.d.ts +0 -16
  183. package/dist/version.d.ts +0 -1
  184. package/dist/webBudget.d.ts +0 -25
  185. package/dist/worktree.d.ts +0 -12
@@ -1,210 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- t
4
- } from "./chunk-R754XIBJ.js";
5
-
6
- // src/oauthLogin.ts
7
- import { createHash, randomBytes } from "crypto";
8
- var OPENROUTER_AUTH_URL = "https://openrouter.ai/auth";
9
- var OPENROUTER_EXCHANGE_URL = "https://openrouter.ai/api/v1/auth/keys";
10
- var OPENROUTER_DASHBOARD_URL = "https://openrouter.ai/settings/keys";
11
- var OPENROUTER_ENV_KEY = "OPENROUTER_API_KEY";
12
- var KEY_LABEL = "bccli";
13
- var EXCHANGE_TIMEOUT_MS = 15e3;
14
- var DETAIL_MAX = 200;
15
- function pkcePair() {
16
- const verifier = randomBytes(32).toString("base64url");
17
- const challenge = createHash("sha256").update(verifier, "ascii").digest("base64url");
18
- return { verifier, challenge };
19
- }
20
- function buildAuthUrl(challenge) {
21
- const url = new URL(OPENROUTER_AUTH_URL);
22
- url.searchParams.set("code_challenge", challenge);
23
- url.searchParams.set("code_challenge_method", "S256");
24
- url.searchParams.set("key_label", KEY_LABEL);
25
- return url.href;
26
- }
27
- function redact(text, secrets) {
28
- let out = text;
29
- for (const s of secrets) {
30
- if (!s) continue;
31
- out = out.split(s).join("***");
32
- }
33
- return out;
34
- }
35
- function classifyExchange403(detail) {
36
- return detail.toLowerCase().includes("expired") ? "expired" : "rejected";
37
- }
38
- function extractWhitelistDetail(body) {
39
- if (typeof body !== "object" || body === null) return "";
40
- const rec = body;
41
- return typeof rec.error === "string" ? rec.error : typeof rec.message === "string" ? rec.message : "";
42
- }
43
- function safeDetail(raw, secrets) {
44
- return redact(raw, secrets).slice(0, DETAIL_MAX);
45
- }
46
- async function exchangeAuthCode(code, verifier, deps) {
47
- const secrets = [code, verifier];
48
- const timeoutSignal = AbortSignal.timeout(deps.timeoutMs ?? EXCHANGE_TIMEOUT_MS);
49
- const signal = deps.signal ? AbortSignal.any([timeoutSignal, deps.signal]) : timeoutSignal;
50
- let parsed;
51
- let status = 0;
52
- try {
53
- const res = await deps.fetch(OPENROUTER_EXCHANGE_URL, {
54
- method: "POST",
55
- headers: { "Content-Type": "application/json" },
56
- body: JSON.stringify({ code, code_verifier: verifier, code_challenge_method: "S256" }),
57
- signal
58
- });
59
- status = res.status;
60
- if (signal.aborted) {
61
- if (deps.signal?.aborted) return { ok: false, kind: "cancelled", detail: "" };
62
- return { ok: false, kind: "timeout", detail: "" };
63
- }
64
- try {
65
- parsed = await res.json();
66
- } catch (error) {
67
- if (deps.signal?.aborted || timeoutSignal.aborted) throw error;
68
- parsed = void 0;
69
- }
70
- } catch (error) {
71
- if (deps.signal?.aborted) return { ok: false, kind: "cancelled", detail: "" };
72
- if (timeoutSignal.aborted) return { ok: false, kind: "timeout", detail: "" };
73
- return { ok: false, kind: "network", detail: safeDetail(String(error?.message ?? ""), secrets) };
74
- }
75
- const detail = safeDetail(extractWhitelistDetail(parsed), secrets);
76
- if (status !== 200) {
77
- if (status === 403) {
78
- return classifyExchange403(detail) === "expired" ? { ok: false, kind: "http403-expired", status: 403, detail } : { ok: false, kind: "http403-rejected", status: 403, detail };
79
- }
80
- return { ok: false, kind: "http-other", status, detail };
81
- }
82
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
83
- return { ok: false, kind: "invalid-body", detail: "" };
84
- }
85
- const key = parsed.key;
86
- if (typeof key !== "string" || key.trim() === "") return { ok: false, kind: "invalid-body", detail: "" };
87
- return { ok: true, key };
88
- }
89
- var ABORT = /* @__PURE__ */ Symbol("aborted");
90
- function isReadCancel(error) {
91
- const e = error;
92
- return e?.name === "AbortError" || e?.code === "SIGINT";
93
- }
94
- async function runOauthLogin(deps) {
95
- if (!deps.isTTY) {
96
- deps.err(t("bccli login openrouter needs a terminal. Use --key to paste an API key instead."));
97
- return 1;
98
- }
99
- const { verifier, challenge } = pkcePair();
100
- const url = buildAuthUrl(challenge);
101
- try {
102
- deps.open(url);
103
- } catch {
104
- deps.out(t("Could not open the browser automatically \u2014 open this URL manually: {url}", { url }));
105
- }
106
- let code;
107
- try {
108
- code = await deps.readLine(t("Paste the authorization code from the browser: "));
109
- } catch (error) {
110
- if (isReadCancel(error)) return 130;
111
- deps.err(
112
- t("Failed to read the authorization code: {reason}. Your stored credential was not changed.", {
113
- reason: String(error?.message ?? error).slice(0, 200)
114
- })
115
- );
116
- return 1;
117
- }
118
- if (code === ABORT) return 130;
119
- if (typeof code !== "string" || code.trim() === "") {
120
- deps.err(t("Authorization code is empty. Run bccli login openrouter again."));
121
- return 1;
122
- }
123
- const trimmed = code.trim();
124
- const secrets = [trimmed, verifier];
125
- const result = await (async () => {
126
- const controller = new AbortController();
127
- const onSigint = () => controller.abort();
128
- process.once("SIGINT", onSigint);
129
- try {
130
- return await exchangeAuthCode(trimmed, verifier, {
131
- fetch: deps.fetch,
132
- timeoutMs: deps.timeoutMs,
133
- signal: controller.signal
134
- });
135
- } finally {
136
- process.removeListener("SIGINT", onSigint);
137
- }
138
- })();
139
- if (!result.ok) {
140
- const detail = redact(result.detail, secrets);
141
- switch (result.kind) {
142
- case "cancelled":
143
- deps.err(t("Login cancelled."));
144
- return 130;
145
- case "timeout":
146
- deps.err(
147
- t("The exchange with OpenRouter timed out after {seconds}s. Your stored credential was not changed.", {
148
- seconds: String(Math.round((deps.timeoutMs ?? EXCHANGE_TIMEOUT_MS) / 1e3))
149
- })
150
- );
151
- return 1;
152
- case "network":
153
- deps.err(t("Could not reach OpenRouter: {reason}. Your stored credential was not changed.", { reason: detail }));
154
- return 1;
155
- case "http403-expired":
156
- deps.err(t("Authorization code expired \u2014 codes are valid for 10 minutes. Run bccli login openrouter again."));
157
- return 1;
158
- case "http403-rejected":
159
- deps.err(t("OpenRouter rejected the authorization code (403): {detail}. Run bccli login openrouter again.", { detail: detail || "no details" }));
160
- return 1;
161
- case "invalid-body":
162
- deps.err(t("OpenRouter did not return an API key in the expected format. Your stored credential was not changed."));
163
- return 1;
164
- case "http-other":
165
- deps.err(t("OpenRouter exchange failed ({status}): {detail}. Your stored credential was not changed.", { status: String(result.status ?? 0), detail }));
166
- return 1;
167
- }
168
- }
169
- try {
170
- deps.saveCredential("openrouter", result.key);
171
- } catch (error) {
172
- deps.err(
173
- t("The key was created on your OpenRouter account but could not be saved locally ({reason}). Manage it (copy or revoke) at {dashboard}, then run bccli login openrouter again.", {
174
- reason: redact(String(error?.message ?? ""), [result.key, trimmed, verifier]).slice(0, 200),
175
- dashboard: OPENROUTER_DASHBOARD_URL
176
- })
177
- );
178
- return 1;
179
- }
180
- deps.out(t("Logged in: openrouter credential saved."));
181
- if (deps.env[OPENROUTER_ENV_KEY]) {
182
- deps.out(t("Note: {env} is also set in the environment; the stored credential takes priority and {env} is only the fallback.", { env: OPENROUTER_ENV_KEY }));
183
- }
184
- deps.out(t("Verify with: bccli models openrouter (your key is never printed)."));
185
- return 0;
186
- }
187
- async function runOauthLogout(deps) {
188
- const removed = deps.removeCredential("openrouter");
189
- if (removed) deps.out(t("openrouter credential removed."));
190
- else deps.out(t("openrouter credential was not stored."));
191
- if (deps.env[OPENROUTER_ENV_KEY]) {
192
- deps.out(t("Note: {env} is still set in the environment and will keep being used.", { env: OPENROUTER_ENV_KEY }));
193
- }
194
- deps.out(t("To revoke keys on your account, open {dashboard}.", { dashboard: OPENROUTER_DASHBOARD_URL }));
195
- return 0;
196
- }
197
- export {
198
- ABORT,
199
- OPENROUTER_AUTH_URL,
200
- OPENROUTER_DASHBOARD_URL,
201
- OPENROUTER_ENV_KEY,
202
- OPENROUTER_EXCHANGE_URL,
203
- buildAuthUrl,
204
- classifyExchange403,
205
- exchangeAuthCode,
206
- pkcePair,
207
- redact,
208
- runOauthLogin,
209
- runOauthLogout
210
- };
@@ -1,47 +0,0 @@
1
- export declare const OPENROUTER_AUTH_URL = "https://openrouter.ai/auth";
2
- export declare const OPENROUTER_EXCHANGE_URL = "https://openrouter.ai/api/v1/auth/keys";
3
- export declare const OPENROUTER_DASHBOARD_URL = "https://openrouter.ai/settings/keys";
4
- export declare const OPENROUTER_ENV_KEY = "OPENROUTER_API_KEY";
5
- export declare function pkcePair(): {
6
- verifier: string;
7
- challenge: string;
8
- };
9
- /** Varian tanpa redirect per docs resmi OpenRouter: kode ditampilkan untuk di-copy. */
10
- export declare function buildAuthUrl(challenge: string): string;
11
- export declare function redact(text: string, secrets: string[]): string;
12
- export declare function classifyExchange403(detail: string): 'expired' | 'rejected';
13
- export type ExchangeResult = {
14
- ok: true;
15
- key: string;
16
- } | {
17
- ok: false;
18
- kind: 'timeout' | 'network' | 'cancelled' | 'http403-expired' | 'http403-rejected' | 'http-other' | 'invalid-body';
19
- status?: number;
20
- detail: string;
21
- };
22
- export declare function exchangeAuthCode(code: string, verifier: string, deps: {
23
- fetch: typeof fetch;
24
- timeoutMs?: number;
25
- signal?: AbortSignal;
26
- }): Promise<ExchangeResult>;
27
- /** Balasan readLine saat Ctrl+C pada mode test; di produksi readLine exit 130 langsung. */
28
- export declare const ABORT: unique symbol;
29
- export interface OauthLoginDeps {
30
- out(s: string): void;
31
- err(s: string): void;
32
- env: NodeJS.ProcessEnv;
33
- isTTY: boolean;
34
- open(url: string): void;
35
- readLine(prompt: string): Promise<string | typeof ABORT>;
36
- saveCredential(id: string, key: string): void;
37
- fetch: typeof fetch;
38
- timeoutMs?: number;
39
- }
40
- export declare function runOauthLogin(deps: OauthLoginDeps): Promise<number>;
41
- export interface OauthLogoutDeps {
42
- out(s: string): void;
43
- err(s: string): void;
44
- env: NodeJS.ProcessEnv;
45
- removeCredential(id: string): boolean;
46
- }
47
- export declare function runOauthLogout(deps: OauthLogoutDeps): Promise<number>;
@@ -1,30 +0,0 @@
1
- import type { PermissionMode } from './config';
2
- import type { PermissionKind } from './tools/types';
3
- export interface PermissionRequest {
4
- tool: string;
5
- kind: PermissionKind;
6
- target: string;
7
- }
8
- export type Decision = 'allow' | 'deny' | 'ask';
9
- export declare const MODE_ORDER: PermissionMode[];
10
- export declare function nextMode(mode: PermissionMode): PermissionMode;
11
- export declare function requiresSensitiveToolApproval(req: PermissionRequest): boolean;
12
- export declare class Permissions {
13
- mode: PermissionMode;
14
- private readonly cwd;
15
- private readonly rules;
16
- private readonly configRules;
17
- constructor(mode: PermissionMode, rules?: string[], cwd?: string);
18
- /** Edits may only be auto-allowed inside the project and outside .git. */
19
- private editInProject;
20
- /** Rules that "[a] allow for this session" would add; undefined when it must always be asked. */
21
- rulesFor(req: PermissionRequest): string[] | undefined;
22
- check(req: PermissionRequest): Decision;
23
- list(): {
24
- rule: string;
25
- source: 'config' | 'session';
26
- }[];
27
- /** Removes a session grant; rules from config/flags stay until the user edits them. */
28
- revoke(rule: string): void;
29
- allowForSession(req: PermissionRequest): void;
30
- }
@@ -1,25 +0,0 @@
1
- /**
2
- * Strict npm spec parsing and managed-entry validation for `bccli plugins`.
3
- * Pure module — no filesystem or process access.
4
- */
5
- export interface ParsedSpec {
6
- /** Package name without the @version/@tag suffix; scope preserved. */
7
- name: string;
8
- /** The original (trimmed) spec, safe to hand to npm argv. */
9
- spec: string;
10
- }
11
- /**
12
- * Parses an npm install spec. Rejects URLs, git+ssh/github:/file: refs, local
13
- * paths, flag-like arguments (leading '-'), uppercase, spaces/metachars and
14
- * npm ranges (^, ~). Returns null when anything is off.
15
- */
16
- export declare function parseNpmSpec(raw: string): ParsedSpec | null;
17
- /** Canonical config entry for a managed package (a config STRING, never join()). */
18
- export declare function managedEntry(name: string): string;
19
- export declare function isManagedEntry(entry: string): boolean;
20
- /**
21
- * Strict validation of a managed entry: canonical prefix + bare npm name suffix.
22
- * Returns the bare name, or null when the entry is not managed or is malformed
23
- * (malformed entries must never be handed to npm).
24
- */
25
- export declare function managedEntryName(entry: string): string | null;
package/dist/plugins.d.ts DELETED
@@ -1,45 +0,0 @@
1
- import { z } from 'zod';
2
- import { type Config } from './config';
3
- import type { HookEvent } from './hooks';
4
- import type { Tool, ToolContext, ToolResult } from './tools/types';
5
- export type PluginPayload = {
6
- tool?: string;
7
- input?: unknown;
8
- output?: string;
9
- };
10
- /** What a plugin may do at load time. `config` is the read-only view of the loaded config. */
11
- export interface PluginContext {
12
- cwd: string;
13
- env: NodeJS.ProcessEnv;
14
- config: Readonly<Config>;
15
- registerTool(tool: PluginTool): void;
16
- /** Observe-only in v1: handlers run in order, errors are logged and never break the session. */
17
- on(event: HookEvent, handler: (payload: PluginPayload) => void | Promise<void>): void;
18
- log(message: string): void;
19
- }
20
- /** A tool as declared by a plugin; schema/kind/target get safe defaults. */
21
- export interface PluginTool {
22
- name: string;
23
- description: string;
24
- schema?: z.ZodType;
25
- kind?: Tool['kind'];
26
- target?: (input: unknown) => string;
27
- run(input: never, ctx: ToolContext): Promise<ToolResult>;
28
- }
29
- export interface LoadedPlugins {
30
- tools: Tool[];
31
- emit(event: HookEvent, payload?: PluginPayload): Promise<void>;
32
- }
33
- /**
34
- * Loads the plugins listed in the global config (trusted). Specs may be absolute paths,
35
- * `./`-relative to the config home, or bare package specifiers. Every failure — missing
36
- * file, syntax error, bad activation, name collision — throws ConfigError so a broken
37
- * plugin is visible immediately instead of silently missing.
38
- */
39
- export declare function loadPlugins(specs: string[], opts: {
40
- home: string;
41
- cwd: string;
42
- env: NodeJS.ProcessEnv;
43
- config: Config;
44
- builtinNames: string[];
45
- }): LoadedPlugins;
@@ -1,288 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- bccliHome,
4
- globalConfigPath
5
- } from "./chunk-TLCL6DC7.js";
6
- import {
7
- t
8
- } from "./chunk-R754XIBJ.js";
9
-
10
- // src/pluginsCli.ts
11
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
12
- import { createRequire } from "module";
13
- import { dirname, isAbsolute, join } from "path";
14
-
15
- // src/pluginSpec.ts
16
- var NPM_SPEC_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@[a-z0-9][a-z0-9._+-]*)?$/;
17
- var BARE_NAME_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
18
- var MANAGED_PREFIX = "./plugins/node_modules/";
19
- function parseNpmSpec(raw) {
20
- const spec = raw.trim();
21
- if (!NPM_SPEC_RE.test(spec)) return null;
22
- const at = spec.lastIndexOf("@");
23
- const name = at > 0 ? spec.slice(0, at) : spec;
24
- if (!NPM_SPEC_RE.test(name)) return null;
25
- return { name, spec };
26
- }
27
- function managedEntry(name) {
28
- return `${MANAGED_PREFIX}${name}`;
29
- }
30
- function isManagedEntry(entry) {
31
- return entry.startsWith(MANAGED_PREFIX);
32
- }
33
- function managedEntryName(entry) {
34
- if (!isManagedEntry(entry)) return null;
35
- const rest = entry.slice(MANAGED_PREFIX.length);
36
- return BARE_NAME_RE.test(rest) ? rest : null;
37
- }
38
-
39
- // src/pluginsCli.ts
40
- function ensurePrefixDir(prefix) {
41
- mkdirSync(prefix, { recursive: true });
42
- const pkg = join(prefix, "package.json");
43
- if (!existsSync(pkg)) {
44
- writeFileSync(pkg, `${JSON.stringify({ name: "bccli-plugins", private: true, version: "0.0.0" }, null, 2)}
45
- `);
46
- }
47
- }
48
- async function runPluginsCommand(args, deps) {
49
- const [action, target] = args.subArgs;
50
- const prefix = join(bccliHome(deps.env), "plugins");
51
- switch (action ?? "list") {
52
- case "list":
53
- return listAction(deps);
54
- case "install":
55
- return installAction(target, deps, prefix);
56
- case "remove":
57
- return removeAction(target, deps, prefix);
58
- case "update":
59
- return updateAction(target, deps, prefix);
60
- default:
61
- deps.err(t("Unknown action: {action}. Use list, install <spec>, remove <name>, or update [name].", { action: action ?? "" }));
62
- return 1;
63
- }
64
- }
65
- var require_ = createRequire(import.meta.url);
66
- function resolveEntryPath(entry, home) {
67
- if (isAbsolute(entry)) return entry;
68
- if (entry.startsWith(".")) return join(home, entry);
69
- try {
70
- return require_.resolve(entry);
71
- } catch {
72
- return null;
73
- }
74
- }
75
- function readFailure(action, error, deps) {
76
- deps.err(
77
- t("Cannot read the plugin config while running {action}: {reason} at {path} \u2014 no changes were made; fix the config, then retry.", {
78
- action,
79
- reason: error.message,
80
- path: globalConfigPath(deps.env)
81
- })
82
- );
83
- return 1;
84
- }
85
- function listAction(deps) {
86
- let entries;
87
- try {
88
- entries = deps.readPlugins();
89
- } catch (error) {
90
- return readFailure("list", error, deps);
91
- }
92
- if (!entries.length) {
93
- deps.out(t("No plugins are installed. Install one with: bccli plugins install <spec>"));
94
- return 0;
95
- }
96
- const home = bccliHome(deps.env);
97
- for (const entry of entries) {
98
- const managed = isManagedEntry(entry);
99
- const bare = managed ? managedEntryName(entry) : entry;
100
- const suffix = !managed ? " (manual)" : bare === null ? " (malformed)" : "";
101
- const resolved = resolveEntryPath(entry, home);
102
- if (!resolved || !existsSync(resolved)) {
103
- deps.out(`\u2717 ${entry}${suffix}`);
104
- continue;
105
- }
106
- let version = "";
107
- try {
108
- const pkgPath = statSync(resolved).isDirectory() ? join(resolved, "package.json") : join(dirname(resolved), "package.json");
109
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
110
- if (pkg.version) version = ` (${pkg.version})`;
111
- } catch {
112
- }
113
- deps.out(`\u2713 ${entry}${suffix}${version}`);
114
- }
115
- return 0;
116
- }
117
- function npmFailure(res, deps) {
118
- deps.err(t("npm install failed ({code}): {stderr}", { code: String(res.code), stderr: res.stderr.slice(-400) }));
119
- return 1;
120
- }
121
- async function installAction(target, deps, prefix) {
122
- if (!target) {
123
- deps.err(t("install requires a package spec: bccli plugins install <spec>"));
124
- return 1;
125
- }
126
- const parsed = parseNpmSpec(target);
127
- if (!parsed) {
128
- deps.err(
129
- t("Invalid npm package spec: {spec}. Use a name, @scope/name, name@version, or name@tag \u2014 URLs, git, paths, and flags are not accepted.", { spec: target })
130
- );
131
- return 1;
132
- }
133
- ensurePrefixDir(prefix);
134
- const res = await deps.runNpm(prefix, ["install", parsed.spec]);
135
- if (res.code !== 0) return npmFailure(res, deps);
136
- const entry = managedEntry(parsed.name);
137
- let entries;
138
- try {
139
- entries = deps.readPlugins();
140
- } catch (error) {
141
- deps.err(t("npm finished but the config could not be read: {reason} \u2014 the entry list is unchanged; fix {path}, then rerun the same command (safe to repeat).", { reason: error.message, path: globalConfigPath(deps.env) }));
142
- return 1;
143
- }
144
- const wasRegistered = entries.includes(entry);
145
- try {
146
- deps.savePlugins(wasRegistered ? entries : [...entries, entry]);
147
- } catch (error) {
148
- const reason = error.message;
149
- deps.err(
150
- wasRegistered ? t("npm finished but the config update failed: {reason}. {name} stays enabled through its existing entry {entry}; the package files may already be new. Fix the config error at {path} \u2014 no entry change was required.", { reason, name: parsed.name, entry, path: globalConfigPath(deps.env) }) : t('npm finished but the config update failed: {reason}. {name} is installed but NOT enabled \u2014 fix the config error, then run "bccli plugins install {name}" again (safe to repeat), or add {entry} to {path} manually.', { reason, name: parsed.name, entry, path: globalConfigPath(deps.env) })
151
- );
152
- return 1;
153
- }
154
- deps.out(wasRegistered ? t("{name} is already enabled.", { name: parsed.name }) : t("{name} installed and enabled.", { name: parsed.name }));
155
- return 0;
156
- }
157
- function resolveTarget(target, deps, action) {
158
- if (!target) return "missing-arg";
159
- let entries;
160
- try {
161
- entries = deps.readPlugins();
162
- } catch (error) {
163
- return {
164
- error: t("Cannot read the plugin config while running {action}: {reason} at {path} \u2014 no changes were made; fix the config, then retry.", {
165
- action,
166
- reason: error.message,
167
- path: globalConfigPath(deps.env)
168
- })
169
- };
170
- }
171
- if (entries.includes(target)) {
172
- if (isManagedEntry(target)) {
173
- const name = managedEntryName(target);
174
- if (!name) {
175
- return {
176
- error: t("Malformed managed entry: {entry} \u2014 fix it manually in {path}; bccli will not pass it to npm.", {
177
- entry: target,
178
- path: globalConfigPath(deps.env)
179
- })
180
- };
181
- }
182
- return { entry: target, name };
183
- }
184
- return { error: t("{entry} is a manual plugin entry \u2014 edit the global config.json directly to remove it.", { entry: target }) };
185
- }
186
- const parsed = parseNpmSpec(target);
187
- if (parsed) {
188
- const entry = managedEntry(parsed.name);
189
- if (entries.includes(entry)) return { entry, name: parsed.name };
190
- return { error: t("{name} is not an installed managed plugin.", { name: parsed.name }) };
191
- }
192
- return { error: t("Unknown plugin: {arg}.", { arg: target }) };
193
- }
194
- async function removeAction(target, deps, prefix) {
195
- const r = resolveTarget(target, deps, "remove");
196
- if (r === "missing-arg") {
197
- deps.err(t("remove requires a plugin name: bccli plugins remove <name>"));
198
- return 1;
199
- }
200
- if ("error" in r) {
201
- deps.err(r.error);
202
- return 1;
203
- }
204
- const res = await deps.runNpm(prefix, ["uninstall", r.name]);
205
- if (res.code !== 0) return npmFailure(res, deps);
206
- let next;
207
- try {
208
- next = deps.readPlugins().filter((e) => e !== r.entry);
209
- } catch (error) {
210
- deps.err(
211
- t('npm removed the files of {name} but the plugin config could not be read: {reason} \u2014 the entry list is unchanged; fix {path}, then run "bccli plugins remove {name}" again (safe to repeat).', {
212
- name: r.name,
213
- reason: error.message,
214
- path: globalConfigPath(deps.env)
215
- })
216
- );
217
- return 1;
218
- }
219
- try {
220
- deps.savePlugins(next);
221
- } catch (error) {
222
- deps.err(
223
- t('npm removed the files of {name} but the config entry {entry} remains: {reason}. bccli will refuse to start while that entry is listed \u2014 fix the config error at {path}, then run "bccli plugins remove {name}" again (safe to repeat), or delete the entry manually.', {
224
- name: r.name,
225
- entry: r.entry,
226
- reason: error.message,
227
- path: globalConfigPath(deps.env)
228
- })
229
- );
230
- return 1;
231
- }
232
- deps.out(t("{name} removed.", { name: r.name }));
233
- return 0;
234
- }
235
- async function updateAction(target, deps, prefix) {
236
- let names;
237
- let hadMalformed = false;
238
- if (target === void 0) {
239
- let entries;
240
- try {
241
- entries = deps.readPlugins();
242
- } catch (error) {
243
- return readFailure("update", error, deps);
244
- }
245
- names = [];
246
- for (const entry of entries) {
247
- if (!isManagedEntry(entry)) continue;
248
- const name = managedEntryName(entry);
249
- if (name) names.push(name);
250
- else {
251
- hadMalformed = true;
252
- deps.err(t("Skipping malformed entry: {entry} \u2014 fix it manually in {path}.", { entry, path: globalConfigPath(deps.env) }));
253
- }
254
- }
255
- if (!names.length) {
256
- if (!hadMalformed) deps.out(t("No managed plugins to update."));
257
- return hadMalformed ? 1 : 0;
258
- }
259
- } else {
260
- const r = resolveTarget(target, deps, "update");
261
- if (r === "missing-arg") {
262
- deps.err(t("update requires a plugin name or no argument: bccli plugins update [name]"));
263
- return 1;
264
- }
265
- if ("error" in r) {
266
- deps.err(r.error);
267
- return 1;
268
- }
269
- names = [r.name];
270
- }
271
- ensurePrefixDir(prefix);
272
- let failed = false;
273
- for (const name of names) {
274
- const argv = ["install", `${name}@latest`];
275
- const res = await deps.runNpm(prefix, argv);
276
- if (res.code !== 0) {
277
- failed = true;
278
- deps.err(t("npm install failed ({code}): {stderr}", { code: String(res.code), stderr: res.stderr.slice(-400) }));
279
- } else {
280
- deps.out(t("{name} updated to latest.", { name }));
281
- }
282
- }
283
- return failed || hadMalformed ? 1 : 0;
284
- }
285
- export {
286
- ensurePrefixDir,
287
- runPluginsCommand
288
- };
@@ -1,16 +0,0 @@
1
- import type { CliArgs } from './args';
2
- export interface PluginsDeps {
3
- out(s: string): void;
4
- err(s: string): void;
5
- env: NodeJS.ProcessEnv;
6
- /** argv = argumen setelah npm (mis. ['install','foo']). Prefix = <home>/plugins. */
7
- runNpm(prefix: string, argv: string[]): Promise<{
8
- code: number;
9
- stderr: string;
10
- }>;
11
- readPlugins(): string[];
12
- savePlugins(entries: string[]): void;
13
- }
14
- /** Minimal package.json so `npm install --prefix` behaves deterministically. */
15
- export declare function ensurePrefixDir(prefix: string): void;
16
- export declare function runPluginsCommand(args: CliArgs, deps: PluginsDeps): Promise<number>;