busa-cli 0.21.0 → 0.30.1
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/dist/cli.js +5 -2
- package/dist/index.js +2 -2
- package/dist/{run-CG95MbX_.js → update-check-DXT4d-yb.js} +283 -87
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { i as runCli, r as pkgVersion, t as checkForUpdate } from "./update-check-DXT4d-yb.js";
|
|
3
3
|
//#region src/cli.ts
|
|
4
|
-
|
|
4
|
+
const exitCode = await runCli(process.argv.slice(2));
|
|
5
|
+
const notice = await checkForUpdate("busabase-cli", pkgVersion());
|
|
6
|
+
if (notice) console.error(notice);
|
|
7
|
+
process.exit(exitCode);
|
|
5
8
|
//#endregion
|
|
6
9
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as render, i as runCli, n as HELP, r as pkgVersion, t as checkForUpdate } from "./update-check-DXT4d-yb.js";
|
|
2
2
|
import { DEFAULT_BASE_URL, createBusabaseClient, normalizeBaseUrl } from "busabase-sdk";
|
|
3
|
-
export { DEFAULT_BASE_URL, HELP, createBusabaseClient, normalizeBaseUrl, render, runCli };
|
|
3
|
+
export { DEFAULT_BASE_URL, HELP, checkForUpdate, createBusabaseClient, normalizeBaseUrl, pkgVersion, render, runCli };
|
|
@@ -17,6 +17,167 @@ import { parse, stringify } from "yaml";
|
|
|
17
17
|
import { checkAirApp } from "busabase-sdk/airapp-check";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import { Uint8ArrayReader, ZipReader } from "@zip.js/zip.js";
|
|
20
|
+
const LocaleSchema = z.enum({
|
|
21
|
+
defaultLocale: "en",
|
|
22
|
+
locales: [
|
|
23
|
+
"en",
|
|
24
|
+
"zh-CN",
|
|
25
|
+
"zh-TW",
|
|
26
|
+
"ja",
|
|
27
|
+
"ko",
|
|
28
|
+
"de",
|
|
29
|
+
"fr",
|
|
30
|
+
"es",
|
|
31
|
+
"pt"
|
|
32
|
+
],
|
|
33
|
+
extendLocales: [
|
|
34
|
+
"en",
|
|
35
|
+
"zh-CN",
|
|
36
|
+
"zh-TW",
|
|
37
|
+
"ja",
|
|
38
|
+
"ko",
|
|
39
|
+
"fr",
|
|
40
|
+
"de",
|
|
41
|
+
"es",
|
|
42
|
+
"ru",
|
|
43
|
+
"it",
|
|
44
|
+
"vi",
|
|
45
|
+
"pt"
|
|
46
|
+
]
|
|
47
|
+
}.locales);
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region ../../packages/openlib/i18n/i-string.ts
|
|
50
|
+
/**
|
|
51
|
+
* i18n string utilities and types / i18n 字符串工具与类型
|
|
52
|
+
*
|
|
53
|
+
* Purpose / 作用
|
|
54
|
+
* - EN: Provide a lightweight, persistable representation for business strings that may be multilingual.
|
|
55
|
+
* Values can be plain strings or locale-keyed records, allowing storage and logic usage.
|
|
56
|
+
* - ZH: 为“可持久化且可多语言”的业务字符串提供轻量表示形式;可存纯文本,或按语言分组的对象,
|
|
57
|
+
* 方便在业务逻辑内读取、比较与分支判断。
|
|
58
|
+
*
|
|
59
|
+
* Model (iString) / 模型(iString)
|
|
60
|
+
* - EN: A value is either a string or a record like { en: "Hello", zh: "你好" }.
|
|
61
|
+
* - ZH: 值可以是纯字符串,或形如 { en: "Hello", zh: "你好" } 的按语言分布对象。
|
|
62
|
+
*
|
|
63
|
+
* Validation / 校验
|
|
64
|
+
* - EN: Zod schemas validate both the record shape and the union type.
|
|
65
|
+
* - ZH: 通过 Zod 对记录结构以及联合类型进行校验,Locale 受 ./types 中定义约束。
|
|
66
|
+
*
|
|
67
|
+
* Exports / 导出
|
|
68
|
+
* - iStringRecordSchema: zod schema for { [locale]: string } constrained by supported locales.
|
|
69
|
+
* - iStringSchema: union of string | iStringRecordSchema.
|
|
70
|
+
* - isIString(str):
|
|
71
|
+
* - EN: Quick check for JSON iString records; returns parsed record or false.
|
|
72
|
+
* - ZH: 快速检测 JSON 字符串是否为 iString 记录;成功返回解析对象,否则返回 false。
|
|
73
|
+
* - iStringMatch(str, compare, lang?):
|
|
74
|
+
* - EN: Compare by specified locale or any locale; supports plain string.
|
|
75
|
+
* - ZH: 支持按指定语言或任一语言进行等值比较;纯字符串直接比较。
|
|
76
|
+
* - iStringParse(str, lang='en'):
|
|
77
|
+
* - EN: Resolve to a display string with sensible fallback.
|
|
78
|
+
* - ZH: 解析为用于展示的字符串,包含合理的回退策略。
|
|
79
|
+
* - iStringify(str, lang='en'):
|
|
80
|
+
* - EN: Wrap a plain string into a record using the given locale.
|
|
81
|
+
* - ZH: 将纯字符串按给定语言包装为记录对象。
|
|
82
|
+
* - iStringConcat(str):
|
|
83
|
+
* - EN: Concatenate all locale values for display/debug (order not guaranteed).
|
|
84
|
+
* - ZH: 拼接所有语言值以便展示/调试(顺序不保证)。
|
|
85
|
+
*
|
|
86
|
+
* Fallback behavior (iStringParse) / 回退策略(iStringParse)
|
|
87
|
+
* - EN: If str is a string, return it. If it is a record: prefer requested locale; else any non-"en"; else "en".
|
|
88
|
+
* Warn and return empty string if no suitable value exists.
|
|
89
|
+
* - ZH: 若为纯字符串,原样返回;若为记录:优先使用请求的语言;否则使用任一非 "en";再退回 "en"。
|
|
90
|
+
* 若均不可用,会发出警告并返回空字符串。
|
|
91
|
+
*
|
|
92
|
+
* Examples / 示例
|
|
93
|
+
* - iStringify('Hello') => { en: 'Hello' }
|
|
94
|
+
* - iStringParse({ en: 'Hello', zh: '你好' }, 'zh') => '你好'
|
|
95
|
+
* - iStringMatch({ en: 'Hello' }, 'Hello') => true
|
|
96
|
+
*
|
|
97
|
+
* Notes / 说明
|
|
98
|
+
* - EN: Depends on zod and the Locale/LocaleSchema from ./types to keep locales consistent.
|
|
99
|
+
* - ZH: 依赖 zod 以及 ./types 中的 Locale/LocaleSchema,确保语言键一致与受控。
|
|
100
|
+
* - EN: isIString expects a JSON string that decodes to a locale-keyed object; plain strings return false.
|
|
101
|
+
* - ZH: isIString 接受 JSON 字符串并尝试解析为按语言分布的对象;纯字符串将返回 false。
|
|
102
|
+
*/
|
|
103
|
+
const iStringRecordSchema = z.partialRecord(LocaleSchema, z.string());
|
|
104
|
+
const iStringSchema = z.union([z.string(), iStringRecordSchema]).describe("i18n string");
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region ../../packages/busabase-contract/src/contract/node-agent-prompt-schemas.ts
|
|
107
|
+
/**
|
|
108
|
+
* Per-node custom scenario prompts (`node.metadata.agentPrompts`) — the single
|
|
109
|
+
* schema shared by BOTH write and read paths, per
|
|
110
|
+
* `apps/busabase-cloud/content/spec/node-agent-prompts-v2.md` §7.3:
|
|
111
|
+
*
|
|
112
|
+
* - Write: `busabase-cli nodes set-agent-prompts` validates a file against this
|
|
113
|
+
* schema client-side, before calling `nodes.updateMetadata` — a malformed file
|
|
114
|
+
* never reaches the network.
|
|
115
|
+
* - Read: `buildNodeAgentPrompts` (`packages/busabase-core/.../node-agent-prompts.ts`)
|
|
116
|
+
* `.safeParse`s whatever is stored in `metadata.agentPrompts` and falls back to
|
|
117
|
+
* the node type's default scenarios on failure, so a bad write (or a manual
|
|
118
|
+
* jsonb edit) can never crash the Agent Prompts dialog.
|
|
119
|
+
*
|
|
120
|
+
* One schema, one place to edit the limits — do not duplicate this validation on
|
|
121
|
+
* either side.
|
|
122
|
+
*/
|
|
123
|
+
/** Recommended limits from the spec (§7.3) — kept as named constants so a limit
|
|
124
|
+
* change has exactly one place to edit, and so error messages can cite them. */
|
|
125
|
+
const CUSTOM_AGENT_PROMPT_LIMITS = {
|
|
126
|
+
/** Max custom prompts per node. */
|
|
127
|
+
maxPrompts: 50,
|
|
128
|
+
/** Max characters per localized `label` value. */
|
|
129
|
+
maxLabelChars: 80,
|
|
130
|
+
/** Max bytes (UTF-8) per localized `body` value. */
|
|
131
|
+
maxBodyBytes: 8192
|
|
132
|
+
};
|
|
133
|
+
/** Same intent vocabulary `PromptDef.intent` uses — `change` is the router-side
|
|
134
|
+
* default when omitted, matching the existing curated-prompt behavior. */
|
|
135
|
+
const customPromptIntentSchema = z.enum(["read-only", "change"]);
|
|
136
|
+
const iStringLocaleValues = (value) => typeof value === "string" ? [value] : Object.values(value);
|
|
137
|
+
/** UTF-8 byte length — `body` includes CJK/JA text, whose per-character byte
|
|
138
|
+
* cost is not 1, so a character-count limit alone would under-enforce §7.3's
|
|
139
|
+
* "8 KiB per localized body" budget. */
|
|
140
|
+
const utf8ByteLength = (value) => new TextEncoder().encode(value).length;
|
|
141
|
+
const customPromptLabelSchema = iStringSchema.refine((value) => iStringLocaleValues(value).every((v) => v.length <= CUSTOM_AGENT_PROMPT_LIMITS.maxLabelChars), { message: `label must be at most ${CUSTOM_AGENT_PROMPT_LIMITS.maxLabelChars} characters per locale` });
|
|
142
|
+
const customPromptBodySchema = iStringSchema.refine((value) => iStringLocaleValues(value).every((v) => utf8ByteLength(v) <= CUSTOM_AGENT_PROMPT_LIMITS.maxBodyBytes), { message: `body must be at most ${CUSTOM_AGENT_PROMPT_LIMITS.maxBodyBytes} bytes (UTF-8) per locale` });
|
|
143
|
+
/**
|
|
144
|
+
* One custom scenario prompt. `body`'s `{target}` placeholder is substituted at
|
|
145
|
+
* render time with the same target string `PromptDef.body(target)` receives
|
|
146
|
+
* today (see `node-agent-prompts.ts`) — this schema does not interpolate it.
|
|
147
|
+
*/
|
|
148
|
+
const customPromptDefSchema = z.object({
|
|
149
|
+
/** Stable id, unique within this node's custom list. */
|
|
150
|
+
key: z.string().trim().min(1, { message: "key must not be empty" }),
|
|
151
|
+
/** Defaults to `change` (same default the curated prompts use) so a prompt
|
|
152
|
+
* cannot silently bypass the approval-first policy by omission. */
|
|
153
|
+
intent: customPromptIntentSchema.optional(),
|
|
154
|
+
/** Short title shown in the dialog's left list. */
|
|
155
|
+
label: customPromptLabelSchema,
|
|
156
|
+
/** Template text; "{target}" is substituted with the rendered target line. */
|
|
157
|
+
body: customPromptBodySchema
|
|
158
|
+
});
|
|
159
|
+
/**
|
|
160
|
+
* The full `metadata.agentPrompts` array. `.max` bounds the list; the
|
|
161
|
+
* `superRefine` catches duplicate keys with an issue path pointing at the
|
|
162
|
+
* offending entry, so a CLI/server validation error names which entry and
|
|
163
|
+
* which key collided instead of a bare "keys must be unique".
|
|
164
|
+
*/
|
|
165
|
+
const customAgentPromptsSchema = z.array(customPromptDefSchema).max(CUSTOM_AGENT_PROMPT_LIMITS.maxPrompts, { message: `at most ${CUSTOM_AGENT_PROMPT_LIMITS.maxPrompts} custom prompts per node` }).superRefine((prompts, ctx) => {
|
|
166
|
+
const firstIndexByKey = /* @__PURE__ */ new Map();
|
|
167
|
+
prompts.forEach((prompt, index) => {
|
|
168
|
+
const firstIndex = firstIndexByKey.get(prompt.key);
|
|
169
|
+
if (firstIndex === void 0) {
|
|
170
|
+
firstIndexByKey.set(prompt.key, index);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
ctx.addIssue({
|
|
174
|
+
code: "custom",
|
|
175
|
+
path: [index, "key"],
|
|
176
|
+
message: `duplicate key "${prompt.key}" — already used at entry ${firstIndex + 1}`
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
//#endregion
|
|
20
181
|
//#region ../../packages/busabase-contract/src/tasks/base-field.ts
|
|
21
182
|
/**
|
|
22
183
|
* Changing a Base's schema, as one task with an `operation` instead of six
|
|
@@ -4957,91 +5118,6 @@ const auditSkill = (files, options = {}) => {
|
|
|
4957
5118
|
return findings;
|
|
4958
5119
|
};
|
|
4959
5120
|
const PACKAGE_SIDECARS = PACKAGE_SKILL_SIDECAR_DIRS.join("|");
|
|
4960
|
-
const LocaleSchema = z.enum({
|
|
4961
|
-
defaultLocale: "en",
|
|
4962
|
-
locales: [
|
|
4963
|
-
"en",
|
|
4964
|
-
"zh-CN",
|
|
4965
|
-
"zh-TW",
|
|
4966
|
-
"ja",
|
|
4967
|
-
"ko",
|
|
4968
|
-
"de",
|
|
4969
|
-
"fr",
|
|
4970
|
-
"es",
|
|
4971
|
-
"pt"
|
|
4972
|
-
],
|
|
4973
|
-
extendLocales: [
|
|
4974
|
-
"en",
|
|
4975
|
-
"zh-CN",
|
|
4976
|
-
"zh-TW",
|
|
4977
|
-
"ja",
|
|
4978
|
-
"ko",
|
|
4979
|
-
"fr",
|
|
4980
|
-
"de",
|
|
4981
|
-
"es",
|
|
4982
|
-
"ru",
|
|
4983
|
-
"it",
|
|
4984
|
-
"vi",
|
|
4985
|
-
"pt"
|
|
4986
|
-
]
|
|
4987
|
-
}.locales);
|
|
4988
|
-
//#endregion
|
|
4989
|
-
//#region ../../packages/openlib/i18n/i-string.ts
|
|
4990
|
-
/**
|
|
4991
|
-
* i18n string utilities and types / i18n 字符串工具与类型
|
|
4992
|
-
*
|
|
4993
|
-
* Purpose / 作用
|
|
4994
|
-
* - EN: Provide a lightweight, persistable representation for business strings that may be multilingual.
|
|
4995
|
-
* Values can be plain strings or locale-keyed records, allowing storage and logic usage.
|
|
4996
|
-
* - ZH: 为“可持久化且可多语言”的业务字符串提供轻量表示形式;可存纯文本,或按语言分组的对象,
|
|
4997
|
-
* 方便在业务逻辑内读取、比较与分支判断。
|
|
4998
|
-
*
|
|
4999
|
-
* Model (iString) / 模型(iString)
|
|
5000
|
-
* - EN: A value is either a string or a record like { en: "Hello", zh: "你好" }.
|
|
5001
|
-
* - ZH: 值可以是纯字符串,或形如 { en: "Hello", zh: "你好" } 的按语言分布对象。
|
|
5002
|
-
*
|
|
5003
|
-
* Validation / 校验
|
|
5004
|
-
* - EN: Zod schemas validate both the record shape and the union type.
|
|
5005
|
-
* - ZH: 通过 Zod 对记录结构以及联合类型进行校验,Locale 受 ./types 中定义约束。
|
|
5006
|
-
*
|
|
5007
|
-
* Exports / 导出
|
|
5008
|
-
* - iStringRecordSchema: zod schema for { [locale]: string } constrained by supported locales.
|
|
5009
|
-
* - iStringSchema: union of string | iStringRecordSchema.
|
|
5010
|
-
* - isIString(str):
|
|
5011
|
-
* - EN: Quick check for JSON iString records; returns parsed record or false.
|
|
5012
|
-
* - ZH: 快速检测 JSON 字符串是否为 iString 记录;成功返回解析对象,否则返回 false。
|
|
5013
|
-
* - iStringMatch(str, compare, lang?):
|
|
5014
|
-
* - EN: Compare by specified locale or any locale; supports plain string.
|
|
5015
|
-
* - ZH: 支持按指定语言或任一语言进行等值比较;纯字符串直接比较。
|
|
5016
|
-
* - iStringParse(str, lang='en'):
|
|
5017
|
-
* - EN: Resolve to a display string with sensible fallback.
|
|
5018
|
-
* - ZH: 解析为用于展示的字符串,包含合理的回退策略。
|
|
5019
|
-
* - iStringify(str, lang='en'):
|
|
5020
|
-
* - EN: Wrap a plain string into a record using the given locale.
|
|
5021
|
-
* - ZH: 将纯字符串按给定语言包装为记录对象。
|
|
5022
|
-
* - iStringConcat(str):
|
|
5023
|
-
* - EN: Concatenate all locale values for display/debug (order not guaranteed).
|
|
5024
|
-
* - ZH: 拼接所有语言值以便展示/调试(顺序不保证)。
|
|
5025
|
-
*
|
|
5026
|
-
* Fallback behavior (iStringParse) / 回退策略(iStringParse)
|
|
5027
|
-
* - EN: If str is a string, return it. If it is a record: prefer requested locale; else any non-"en"; else "en".
|
|
5028
|
-
* Warn and return empty string if no suitable value exists.
|
|
5029
|
-
* - ZH: 若为纯字符串,原样返回;若为记录:优先使用请求的语言;否则使用任一非 "en";再退回 "en"。
|
|
5030
|
-
* 若均不可用,会发出警告并返回空字符串。
|
|
5031
|
-
*
|
|
5032
|
-
* Examples / 示例
|
|
5033
|
-
* - iStringify('Hello') => { en: 'Hello' }
|
|
5034
|
-
* - iStringParse({ en: 'Hello', zh: '你好' }, 'zh') => '你好'
|
|
5035
|
-
* - iStringMatch({ en: 'Hello' }, 'Hello') => true
|
|
5036
|
-
*
|
|
5037
|
-
* Notes / 说明
|
|
5038
|
-
* - EN: Depends on zod and the Locale/LocaleSchema from ./types to keep locales consistent.
|
|
5039
|
-
* - ZH: 依赖 zod 以及 ./types 中的 Locale/LocaleSchema,确保语言键一致与受控。
|
|
5040
|
-
* - EN: isIString expects a JSON string that decodes to a locale-keyed object; plain strings return false.
|
|
5041
|
-
* - ZH: isIString 接受 JSON 字符串并尝试解析为按语言分布的对象;纯字符串将返回 false。
|
|
5042
|
-
*/
|
|
5043
|
-
const iStringRecordSchema = z.partialRecord(LocaleSchema, z.string());
|
|
5044
|
-
z.union([z.string(), iStringRecordSchema]).describe("i18n string");
|
|
5045
5121
|
//#endregion
|
|
5046
5122
|
//#region ../../packages/busabase-contract/src/contract/auto-merge.ts
|
|
5047
5123
|
/**
|
|
@@ -5187,7 +5263,8 @@ const baseSchema = z.object({
|
|
|
5187
5263
|
requiredApprovals: z.number()
|
|
5188
5264
|
}),
|
|
5189
5265
|
createdAt: z.string(),
|
|
5190
|
-
fields: z.array(baseFieldSchema)
|
|
5266
|
+
fields: z.array(baseFieldSchema),
|
|
5267
|
+
metadata: z.record(z.string(), z.unknown()).default({})
|
|
5191
5268
|
});
|
|
5192
5269
|
z.object({
|
|
5193
5270
|
parentNodeId: z.string().optional().describe("Parent node id. Must be a folder or the space root; container-incapable node types (Base, Doc, AirApp, etc.) cannot hold children."),
|
|
@@ -10901,6 +10978,39 @@ Examples:
|
|
|
10901
10978
|
message: opts.message,
|
|
10902
10979
|
submittedBy: opts.submittedBy
|
|
10903
10980
|
})));
|
|
10981
|
+
addGlobalFlags(nodes.command("set-agent-prompts")).description("Set a node's custom scenario prompts, shown in the Agent Prompts dialog instead of the node type's generic ones. Capability prompts (what the node type can actually do) are unaffected.").requiredOption("--node-id <id>", "node id to customize").requiredOption("--file <path>", "JSON file: an array of { key, intent?, label, body } — see the help text below").addHelpText("after", `
|
|
10982
|
+
Each entry: { key: string (unique per node), intent?: "read-only" | "change",
|
|
10983
|
+
label: string | { <locale>: string }, body: string | { <locale>: string } }.
|
|
10984
|
+
Locale keys (when using the object form): en, zh-CN, zh-TW, ja, ko, de, fr, es,
|
|
10985
|
+
pt — anything else fails validation as "Invalid input" on that field.
|
|
10986
|
+
"body" may contain a literal "{target}" placeholder, substituted at render time
|
|
10987
|
+
with the same node/space description every built-in prompt uses. At most 50
|
|
10988
|
+
entries per node, 80 characters per localized label, 8 KiB per localized body.
|
|
10989
|
+
|
|
10990
|
+
The file is validated against this exact schema BEFORE any network request —
|
|
10991
|
+
a malformed file (duplicate key, an over-limit label/body, an unrecognized
|
|
10992
|
+
locale) is reported entry-by-entry and nothing is written. \`--file\`'s
|
|
10993
|
+
contents ARE the array to store; this command does not reshape it.
|
|
10994
|
+
|
|
10995
|
+
Examples:
|
|
10996
|
+
busabase-cli nodes set-agent-prompts --node-id nod_123 --file prompts.json`).action(runAction(state, (client, opts) => {
|
|
10997
|
+
const filePath = opts.file;
|
|
10998
|
+
let raw;
|
|
10999
|
+
try {
|
|
11000
|
+
raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
11001
|
+
} catch (error) {
|
|
11002
|
+
throw new CliOutcomeError("VALIDATION", `Could not read/parse ${filePath} as JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
11003
|
+
}
|
|
11004
|
+
const parsed = customAgentPromptsSchema.safeParse(raw);
|
|
11005
|
+
if (!parsed.success) {
|
|
11006
|
+
const detail = formatIssues(parsed.error.issues);
|
|
11007
|
+
throw new CliOutcomeError("VALIDATION", `Invalid agent prompts in ${filePath}${detail ? ` — ${detail}` : ""}`);
|
|
11008
|
+
}
|
|
11009
|
+
return client.nodes.updateMetadata({
|
|
11010
|
+
nodeId: opts.nodeId,
|
|
11011
|
+
metadata: { agentPrompts: parsed.data }
|
|
11012
|
+
});
|
|
11013
|
+
}));
|
|
10904
11014
|
const bases = program.command("bases").description("Bases (structured tables)");
|
|
10905
11015
|
addGlobalFlags(bases.command("list")).description("List Bases in the active space").addOption(new Option("--status <active|archived>", "live Bases (default) or archived ones").choices(["active", "archived"])).action(runAction(state, (client, opts) => client.bases.list({ status: opts.status ?? "active" })));
|
|
10906
11016
|
addGlobalFlags(bases.command("get")).description("Get one Base by slug").requiredOption("--slug <slug>", "Base slug").action(runAction(state, async (client, opts) => {
|
|
@@ -11327,4 +11437,90 @@ function explainError(error, config) {
|
|
|
11327
11437
|
return `${body}\n → Docs: ${DOCS_TROUBLESHOOTING}`;
|
|
11328
11438
|
}
|
|
11329
11439
|
//#endregion
|
|
11330
|
-
|
|
11440
|
+
//#region src/update-check.ts
|
|
11441
|
+
/** 24h — frequent enough to notice a new release, rare enough to never matter for latency. */
|
|
11442
|
+
const CHECK_INTERVAL_MS = 864e5;
|
|
11443
|
+
/**
|
|
11444
|
+
* Only paid on the (at most once per 24h, per package) cache-refresh path, and
|
|
11445
|
+
* only for a real network round trip — not on every command. A live check
|
|
11446
|
+
* against the real npm registry measured a cold DNS+TLS handshake at ~800ms
|
|
11447
|
+
* on its own, so anything near that cuts it too close and turns "occasionally
|
|
11448
|
+
* shows the notice a run late" into "usually misses it on the first try
|
|
11449
|
+
* after 24h" — 1500ms leaves real headroom while still being far short of
|
|
11450
|
+
* anything a human would perceive as the command hanging.
|
|
11451
|
+
*/
|
|
11452
|
+
const FETCH_TIMEOUT_MS = 1500;
|
|
11453
|
+
function cacheDir() {
|
|
11454
|
+
return process.env.BUSABASE_UPDATE_CACHE_DIR || join(homedir(), ".cache", "busabase");
|
|
11455
|
+
}
|
|
11456
|
+
function cacheFile() {
|
|
11457
|
+
return join(cacheDir(), "update-check.json");
|
|
11458
|
+
}
|
|
11459
|
+
async function readCache() {
|
|
11460
|
+
try {
|
|
11461
|
+
const raw = await readFile(cacheFile(), "utf8");
|
|
11462
|
+
const parsed = JSON.parse(raw);
|
|
11463
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
11464
|
+
} catch {
|
|
11465
|
+
return {};
|
|
11466
|
+
}
|
|
11467
|
+
}
|
|
11468
|
+
async function writeCacheEntry(packageName, entry) {
|
|
11469
|
+
try {
|
|
11470
|
+
await mkdir(cacheDir(), { recursive: true });
|
|
11471
|
+
const all = await readCache();
|
|
11472
|
+
all[packageName] = entry;
|
|
11473
|
+
await writeFile(cacheFile(), JSON.stringify(all), "utf8");
|
|
11474
|
+
} catch {}
|
|
11475
|
+
}
|
|
11476
|
+
async function fetchLatestVersion(packageName) {
|
|
11477
|
+
const controller = new AbortController();
|
|
11478
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
11479
|
+
try {
|
|
11480
|
+
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`, { signal: controller.signal });
|
|
11481
|
+
if (!res.ok) return null;
|
|
11482
|
+
const data = await res.json();
|
|
11483
|
+
return typeof data.version === "string" ? data.version : null;
|
|
11484
|
+
} catch {
|
|
11485
|
+
return null;
|
|
11486
|
+
} finally {
|
|
11487
|
+
clearTimeout(timeout);
|
|
11488
|
+
}
|
|
11489
|
+
}
|
|
11490
|
+
/** Opted out via env, CI, or a non-interactive terminal — a notice nobody will read. */
|
|
11491
|
+
function checksDisabled() {
|
|
11492
|
+
if (process.env.BUSABASE_NO_UPDATE_CHECK) return true;
|
|
11493
|
+
if (process.env.CI === "true") return true;
|
|
11494
|
+
if (!process.stdout.isTTY) return true;
|
|
11495
|
+
return false;
|
|
11496
|
+
}
|
|
11497
|
+
function formatNotice(packageName, currentVersion, latestVersion) {
|
|
11498
|
+
return `\nA new version of ${packageName} is available: ${currentVersion} → ${latestVersion}\nRun \`npm i -g ${packageName}@latest\` to update.\n`;
|
|
11499
|
+
}
|
|
11500
|
+
/**
|
|
11501
|
+
* Returns an update notice string if a newer version is known, else `null` —
|
|
11502
|
+
* caller decides where that goes (stderr for `busabase-cli`, the server boot
|
|
11503
|
+
* banner for `busabase`). Never throws.
|
|
11504
|
+
*
|
|
11505
|
+
* Cache hit (checked within the last 24h): synchronous-fast, no network call.
|
|
11506
|
+
* Cache miss/stale: pays for ONE bounded (`FETCH_TIMEOUT_MS`) registry round
|
|
11507
|
+
* trip so this run can already show the notice if a new version just shipped;
|
|
11508
|
+
* a genuinely slow/dead network just means this run stays silent and the next
|
|
11509
|
+
* stale check tries again — the timeout caps the worst case, it doesn't
|
|
11510
|
+
* silently retry in the background past process exit (a short-lived CLI
|
|
11511
|
+
* process can't rely on a detached background task actually finishing).
|
|
11512
|
+
*/
|
|
11513
|
+
async function checkForUpdate(packageName, currentVersion) {
|
|
11514
|
+
if (checksDisabled()) return null;
|
|
11515
|
+
const cached = (await readCache())[packageName];
|
|
11516
|
+
if (!(!cached || Date.now() - cached.lastCheckedAt > CHECK_INTERVAL_MS)) return cached.latestVersion !== currentVersion ? formatNotice(packageName, currentVersion, cached.latestVersion) : null;
|
|
11517
|
+
const latest = await fetchLatestVersion(packageName);
|
|
11518
|
+
if (!latest) return null;
|
|
11519
|
+
await writeCacheEntry(packageName, {
|
|
11520
|
+
lastCheckedAt: Date.now(),
|
|
11521
|
+
latestVersion: latest
|
|
11522
|
+
});
|
|
11523
|
+
return latest !== currentVersion ? formatNotice(packageName, currentVersion, latest) : null;
|
|
11524
|
+
}
|
|
11525
|
+
//#endregion
|
|
11526
|
+
export { render as a, runCli as i, HELP as n, pkgVersion as r, checkForUpdate as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "busa-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.1",
|
|
4
4
|
"description": "Command-line client for the Busabase OpenAPI REST API. Talks to a local or remote `busabase server`. Short-name alias for busabase-cli.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/busabase/busabase/tree/main/apps/busabase-cli",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@zip.js/zip.js": "^2.8.20",
|
|
35
35
|
"busabase-package": "workspace:*",
|
|
36
|
-
"busabase-sdk": "0.
|
|
36
|
+
"busabase-sdk": "0.30.1",
|
|
37
37
|
"commander": "^14.0.0",
|
|
38
38
|
"tar-stream": "^3.1.7",
|
|
39
39
|
"yaml": "^2.9.0",
|