@letrquan/book 0.3.0 → 0.4.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1024 -0
- package/README.md +6 -3
- package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
- package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
- package/dist/app-J7BVPSUH.js.map +1 -0
- package/dist/chunk-32WBCUAN.js +660 -0
- package/dist/chunk-32WBCUAN.js.map +1 -0
- package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
- package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
- package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
- package/dist/chunk-AHC7OU75.js.map +1 -0
- package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
- package/dist/chunk-AJZWLKLQ.js.map +1 -0
- package/dist/chunk-B5FC3MYZ.js +395 -0
- package/dist/chunk-B5FC3MYZ.js.map +1 -0
- package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
- package/dist/chunk-DNFQTSMJ.js.map +1 -0
- package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
- package/dist/chunk-E5WLQSKA.js.map +1 -0
- package/dist/chunk-ECLC4CIJ.js +1181 -0
- package/dist/chunk-ECLC4CIJ.js.map +1 -0
- package/dist/chunk-EEX6QNXV.js +127 -0
- package/dist/chunk-EEX6QNXV.js.map +1 -0
- package/dist/chunk-EFXVSQ5J.js +931 -0
- package/dist/chunk-EFXVSQ5J.js.map +1 -0
- package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
- package/dist/chunk-ENLB7NYV.js.map +1 -0
- package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
- package/dist/chunk-FPR7SKIE.js.map +1 -0
- package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
- package/dist/chunk-HFB74CLM.js.map +1 -0
- package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
- package/dist/chunk-HOZXKOE3.js.map +1 -0
- package/dist/chunk-IDYWN37T.js +36 -0
- package/dist/chunk-IDYWN37T.js.map +1 -0
- package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
- package/dist/chunk-JGF5HLSI.js.map +1 -0
- package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
- package/dist/chunk-KDNWMKXV.js.map +1 -0
- package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
- package/dist/chunk-MEOB2DZA.js.map +1 -0
- package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
- package/dist/chunk-N2G4Q7ME.js +2672 -0
- package/dist/chunk-N2G4Q7ME.js.map +1 -0
- package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
- package/dist/chunk-OJBMDM5H.js.map +1 -0
- package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
- package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
- package/dist/chunk-REASC2QG.js.map +1 -0
- package/dist/chunk-SBTU5BEC.js +114 -0
- package/dist/chunk-SBTU5BEC.js.map +1 -0
- package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
- package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
- package/dist/chunk-UCFJ43OS.js.map +1 -0
- package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
- package/dist/chunk-W7SLYS3M.js.map +1 -0
- package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
- package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
- package/dist/chunk-XJRJVXJR.js.map +1 -0
- package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
- package/dist/chunk-YIL6HTJX.js.map +1 -0
- package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
- package/dist/context-C3RAP6K5.js +41 -0
- package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
- package/dist/index.js +657 -754
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
- package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
- package/dist/job-runner.js +10 -3
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.js +13 -4
- package/dist/job-supervisor.js.map +1 -1
- package/dist/loader-DQNPDKOX.js +27 -0
- package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
- package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
- package/dist/memory-store-I6Z54VUN.js +51 -0
- package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
- package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
- package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
- package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
- package/dist/provider-approvals-5AGH3RVT.js +76 -0
- package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
- package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
- package/dist/scrollback-GD76GNM4.js.map +1 -0
- package/dist/sdk.d.ts +876 -64
- package/dist/sdk.js +57 -37
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-KDBH45AQ.js +43 -0
- package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
- package/dist/shell-selection-Z5WORZU4.js +31 -0
- package/dist/shell-selection-Z5WORZU4.js.map +1 -0
- package/package.json +5 -5
- package/dist/app-GE4OHJOC.js.map +0 -1
- package/dist/chunk-224FJZLH.js.map +0 -1
- package/dist/chunk-5RDRAO4B.js.map +0 -1
- package/dist/chunk-5YHTKWJN.js.map +0 -1
- package/dist/chunk-62QL5QUP.js.map +0 -1
- package/dist/chunk-67UZEWWF.js.map +0 -1
- package/dist/chunk-6OQQOMZR.js +0 -785
- package/dist/chunk-6OQQOMZR.js.map +0 -1
- package/dist/chunk-AQEQX4FF.js.map +0 -1
- package/dist/chunk-C7ADUNPR.js +0 -273
- package/dist/chunk-C7ADUNPR.js.map +0 -1
- package/dist/chunk-EFJ2NH6E.js.map +0 -1
- package/dist/chunk-IME7R2UI.js.map +0 -1
- package/dist/chunk-J62VZYOB.js +0 -1339
- package/dist/chunk-J62VZYOB.js.map +0 -1
- package/dist/chunk-JTPOI3KU.js.map +0 -1
- package/dist/chunk-QYBD22H5.js.map +0 -1
- package/dist/chunk-RFJ6Q6LE.js.map +0 -1
- package/dist/chunk-S4JMID4G.js +0 -259
- package/dist/chunk-S4JMID4G.js.map +0 -1
- package/dist/chunk-TLX3H2CL.js.map +0 -1
- package/dist/chunk-VPNLCMVR.js.map +0 -1
- package/dist/chunk-W3XFHEF4.js.map +0 -1
- package/dist/chunk-X4NJT436.js.map +0 -1
- package/dist/chunk-X66I2NQO.js +0 -137
- package/dist/chunk-X66I2NQO.js.map +0 -1
- package/dist/chunk-Y2VHN7TC.js.map +0 -1
- package/dist/chunk-YC6W4EJH.js.map +0 -1
- package/dist/context-CBOBDY5Q.js +0 -38
- package/dist/loader-S77OVTHH.js +0 -18
- package/dist/memory-store-DZUT5HUH.js +0 -75
- package/dist/scrollback-35ME2GFX.js.map +0 -1
- package/dist/settings-loader-JUNPGAAM.js +0 -35
- package/dist/shell-selection-CDHGJ4GC.js +0 -17
- /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
- /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
- /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
- /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
- /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
- /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
- /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
- /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
- /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
- /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
- /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
- /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
- /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
- /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
- /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
- /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
- /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
- /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
- /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
- /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
- /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
- /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
projectCommandChoiceSchema,
|
|
5
5
|
projectDirectoryChoiceSchema,
|
|
6
6
|
projectHookChoiceSchema,
|
|
7
|
+
workspaceProviderChoiceSchema,
|
|
7
8
|
writeFileAtomic
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-REASC2QG.js";
|
|
9
10
|
import {
|
|
10
11
|
resolveBookHome
|
|
11
12
|
} from "./chunk-ZPSZZUWF.js";
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
import { existsSync, readFileSync } from "fs";
|
|
15
16
|
import { join, resolve } from "path";
|
|
16
17
|
import { z } from "zod";
|
|
17
|
-
var TRUST_STORE_VERSION =
|
|
18
|
+
var TRUST_STORE_VERSION = 4;
|
|
18
19
|
var workspaceTrustSchema = z.object({
|
|
19
20
|
/** Keyed by the exact rule text the project layer declared. */
|
|
20
21
|
permissionAllowRules: z.record(z.string(), projectAllowRuleChoiceSchema).default({}),
|
|
@@ -29,7 +30,13 @@ var workspaceTrustSchema = z.object({
|
|
|
29
30
|
* text is what the repository controls; the real path is what the file tools would serve, and
|
|
30
31
|
* so what the user is approving.
|
|
31
32
|
*/
|
|
32
|
-
projectDirectories: z.record(z.string(), projectDirectoryChoiceSchema).default({})
|
|
33
|
+
projectDirectories: z.record(z.string(), projectDirectoryChoiceSchema).default({}),
|
|
34
|
+
/**
|
|
35
|
+
* Keyed by provider id, carrying the fingerprint of the declared type, origin and `apiKey`
|
|
36
|
+
* reference the decision was made against. The id `.bookrc.json` holds the decision about a
|
|
37
|
+
* legacy `baseUrl`, which is not a provider but redirects the same credential.
|
|
38
|
+
*/
|
|
39
|
+
workspaceProviders: z.record(z.string(), workspaceProviderChoiceSchema).default({})
|
|
33
40
|
});
|
|
34
41
|
function emptyWorkspaceTrust() {
|
|
35
42
|
return {
|
|
@@ -37,7 +44,8 @@ function emptyWorkspaceTrust() {
|
|
|
37
44
|
mcpServers: {},
|
|
38
45
|
hookEntries: {},
|
|
39
46
|
projectCommands: {},
|
|
40
|
-
projectDirectories: {}
|
|
47
|
+
projectDirectories: {},
|
|
48
|
+
workspaceProviders: {}
|
|
41
49
|
};
|
|
42
50
|
}
|
|
43
51
|
function defaultTrustStorePath(home) {
|
|
@@ -108,4 +116,4 @@ export {
|
|
|
108
116
|
loadWorkspaceTrust,
|
|
109
117
|
updateWorkspaceTrust
|
|
110
118
|
};
|
|
111
|
-
//# sourceMappingURL=chunk-
|
|
119
|
+
//# sourceMappingURL=chunk-HFB74CLM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workspace-trust.ts"],"sourcesContent":["/**\n * Trust decisions the user makes about repository-controlled input.\n *\n * Book gates four kinds of repository-declared configuration behind a one-time\n * per-workspace decision: `permissions.allow` rules, MCP servers, hook entries,\n * and shell substitution in a project `.book/commands/*.md` body. Each gate is\n * only as strong as the store its decisions live in.\n *\n * That store used to be `<workspace>/.book/settings.local.json`, on the\n * reasoning that the file is gitignored and only Book writes it. `.gitignore`\n * does not stop a *tracked* file from reaching a clone: `git add -f\n * .book/settings.local.json` ships it with the repository, and every\n * fingerprint the store is keyed by is a digest of configuration the repository\n * already controls. A hostile project could therefore precompute its own\n * approvals and arrive pre-trusted, releasing its hooks on first run.\n *\n * Approval data lives outside the workspace instead, in\n * `<BOOK_HOME>/trust.json`, keyed by absolute workspace path. Nothing a\n * repository can write reaches it, so the gated party cannot self-approve\n * whatever it does to the files it ships.\n *\n * Reads are fail-closed: an unparseable or off-schema store resolves to \"no\n * decisions recorded\", which withholds the gated input rather than releasing\n * it. Writes refuse to touch a store they could not parse, so a corrupt or\n * newer-versioned file is reported rather than silently overwritten.\n */\nimport { existsSync, readFileSync } from 'fs';\nimport { join, resolve } from 'path';\nimport { z } from 'zod';\nimport { resolveBookHome } from './book-home.js';\nimport { writeFileAtomic } from './settings-repository.js';\nimport {\n mcpProjectServerChoiceSchema,\n projectAllowRuleChoiceSchema,\n projectCommandChoiceSchema,\n projectDirectoryChoiceSchema,\n projectHookChoiceSchema,\n workspaceProviderChoiceSchema,\n} from './settings.js';\n\n/**\n * Bumped only for a format change that older Book versions cannot read.\n *\n * v2 added `projectCommands`. The addition is *readable* by a v1 build, which\n * is exactly the problem: `updateWorkspaceTrust` writes the schema-parsed\n * value, and `z.object` strips unknown keys, so a v1 build recording any hook,\n * rule, or MCP decision for a workspace would silently drop that workspace's\n * command approvals on the way out. Refusing the whole store is the louder and\n * safer failure — a v1 build reports it as unreadable, withholds every gated\n * declaration, and declines to write, rather than quietly erasing decisions.\n *\n * v3 added `projectDirectories` for the same reason and with the same\n * consequence: a v2 build that records a hook or command decision for a\n * workspace would write the store without that workspace's `additionalDirectories`\n * approvals, and a project directory the user had approved would silently go\n * back to being withheld. Bumping the version is what makes that failure loud\n * instead of silent: an older build reads a v3 store as unreadable, withholds\n * every gated declaration, and declines to write until it is upgraded.\n *\n * v4 added `workspaceProviders`, for the same reason and with the same\n * consequence: a v3 build that records any other decision for a workspace would\n * write the store without that workspace's provider approvals, and a provider\n * the user had approved would silently go back to being withheld. An older\n * build reads a v4 store as unreadable, withholds every gated declaration, and\n * declines to write until it is upgraded.\n */\nexport const TRUST_STORE_VERSION = 4;\n\nexport const workspaceTrustSchema = z.object({\n /** Keyed by the exact rule text the project layer declared. */\n permissionAllowRules: z.record(z.string(), projectAllowRuleChoiceSchema).default({}),\n /** Keyed by server name, carrying the fingerprint the decision was made against. */\n mcpServers: z.record(z.string(), mcpProjectServerChoiceSchema).default({}),\n /** Keyed by a fingerprint of `{ event, matcher, command, env }`. */\n hookEntries: z.record(z.string(), projectHookChoiceSchema).default({}),\n /** Keyed by command name, carrying the fingerprint of the shell its body runs. */\n projectCommands: z.record(z.string(), projectCommandChoiceSchema).default({}),\n /**\n * Keyed by the *real path* of a project-declared `additionalDirectories` entry. The declared\n * text is what the repository controls; the real path is what the file tools would serve, and\n * so what the user is approving.\n */\n projectDirectories: z.record(z.string(), projectDirectoryChoiceSchema).default({}),\n /**\n * Keyed by provider id, carrying the fingerprint of the declared type, origin and `apiKey`\n * reference the decision was made against. The id `.bookrc.json` holds the decision about a\n * legacy `baseUrl`, which is not a provider but redirects the same credential.\n */\n workspaceProviders: z.record(z.string(), workspaceProviderChoiceSchema).default({}),\n});\n\nexport type WorkspaceTrust = z.infer<typeof workspaceTrustSchema>;\n\nexport function emptyWorkspaceTrust(): WorkspaceTrust {\n return {\n permissionAllowRules: {},\n mcpServers: {},\n hookEntries: {},\n projectCommands: {},\n projectDirectories: {},\n workspaceProviders: {},\n };\n}\n\n/** Location of the user-global trust store. */\nexport function defaultTrustStorePath(home?: string): string {\n return home ? join(home, '.book', 'trust.json') : join(resolveBookHome(), 'trust.json');\n}\n\nfunction workspaceKey(workspace: string): string {\n const absolute = resolve(workspace);\n // Windows paths are case-insensitive: without folding, `D:\\repo` and `d:\\repo`\n // would accumulate two independent sets of decisions for one directory, and a\n // decision recorded under one spelling would read back as \"not decided\".\n return process.platform === 'win32' ? absolute.toLowerCase() : absolute;\n}\n\ninterface RawTrustStore {\n /** Other workspaces' entries, preserved verbatim so a write never drops them. */\n workspaces: Record<string, unknown>;\n}\n\ninterface RawTrustStoreRead {\n store: RawTrustStore;\n /** Set when the file exists but could not be understood; blocks writes. */\n unreadable?: string;\n}\n\nfunction readRawStore(path: string): RawTrustStoreRead {\n if (!existsSync(path)) return { store: { workspaces: {} } };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf-8'));\n } catch (error) {\n return {\n store: { workspaces: {} },\n unreadable: `${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return { store: { workspaces: {} }, unreadable: `${path} must contain a JSON object` };\n }\n\n const record = parsed as Record<string, unknown>;\n const version = typeof record.version === 'number' ? record.version : TRUST_STORE_VERSION;\n if (version > TRUST_STORE_VERSION) {\n return {\n store: { workspaces: {} },\n unreadable: `${path} was written by a newer version of Book (trust store v${version}); upgrade before recording new decisions`,\n };\n }\n\n const workspaces = record.workspaces;\n if (workspaces === undefined) return { store: { workspaces: {} } };\n if (workspaces === null || typeof workspaces !== 'object' || Array.isArray(workspaces)) {\n return { store: { workspaces: {} }, unreadable: `${path} \"workspaces\" must be an object` };\n }\n return { store: { workspaces: { ...(workspaces as Record<string, unknown>) } } };\n}\n\n/**\n * Decisions recorded for one workspace. Anything unreadable resolves to no\n * decisions, which withholds the gated input.\n */\nexport function loadWorkspaceTrust(\n workspace: string,\n storePath: string = defaultTrustStorePath(),\n): WorkspaceTrust {\n const { store } = readRawStore(storePath);\n const parsed = workspaceTrustSchema.safeParse(store.workspaces[workspaceKey(workspace)] ?? {});\n return parsed.success ? parsed.data : emptyWorkspaceTrust();\n}\n\n/**\n * Apply a decision to one workspace's record, leaving every other workspace's\n * entry byte-identical.\n */\nexport function updateWorkspaceTrust(\n workspace: string,\n mutate: (trust: WorkspaceTrust) => void,\n storePath: string = defaultTrustStorePath(),\n): { ok: boolean; error?: string } {\n const { store, unreadable } = readRawStore(storePath);\n if (unreadable) return { ok: false, error: unreadable };\n\n const key = workspaceKey(workspace);\n const existing = workspaceTrustSchema.safeParse(store.workspaces[key] ?? {});\n const candidate = existing.success ? existing.data : emptyWorkspaceTrust();\n mutate(candidate);\n\n const validated = workspaceTrustSchema.safeParse(candidate);\n if (!validated.success) return { ok: false, error: validated.error.message };\n\n const next = {\n version: TRUST_STORE_VERSION,\n workspaces: { ...store.workspaces, [key]: validated.data },\n };\n try {\n writeFileAtomic(storePath, `${JSON.stringify(next, null, 2)}\\n`);\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) };\n }\n return { ok: true };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -14,6 +14,24 @@ var REMOVED_BLOCKS = [
|
|
|
14
14
|
'The experimental Zero-Mem capability was removed and with it the "experimental" block. Delete it; compaction is always the production summary strategy now.'
|
|
15
15
|
]
|
|
16
16
|
];
|
|
17
|
+
var REMOVED_NESTED = [
|
|
18
|
+
[
|
|
19
|
+
["memory", "requireApproval"],
|
|
20
|
+
"The memory inbox was removed: the model writes memory files directly. Delete memory.requireApproval; set memory.autoSave to false to make memory read-only."
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
["memory", "quarantineExternal"],
|
|
24
|
+
"The external-content quarantine was removed: a session that read the web or an MCP server writes memory like any other. Delete memory.quarantineExternal."
|
|
25
|
+
],
|
|
26
|
+
[
|
|
27
|
+
["memory", "extraction"],
|
|
28
|
+
"Post-session memory extraction was removed. Delete the memory.extraction block; it is no longer read."
|
|
29
|
+
]
|
|
30
|
+
];
|
|
31
|
+
var PERMISSION_RULE_LISTS = ["allow", "ask", "deny"];
|
|
32
|
+
function namesMemorySave(rule) {
|
|
33
|
+
return typeof rule === "string" && /^\s*MemorySave\s*(\(|$)/.test(rule);
|
|
34
|
+
}
|
|
17
35
|
var REMOVED_AUTH_STORE_FILENAME = "auth.json";
|
|
18
36
|
function removedAuthStoreNotice(path) {
|
|
19
37
|
return `${path} holds an OAuth refresh token from the removed subscription login. Nothing reads it any more \u2014 delete the file, and revoke the token with the provider if it was ever used.`;
|
|
@@ -54,6 +72,24 @@ function collectRemovedSettingNotices(document) {
|
|
|
54
72
|
for (const [key, message] of REMOVED_BLOCKS) {
|
|
55
73
|
if (document[key] !== void 0) notices.push({ key, message });
|
|
56
74
|
}
|
|
75
|
+
for (const [path, message] of REMOVED_NESTED) {
|
|
76
|
+
let node = document;
|
|
77
|
+
for (const segment of path) node = isRecord(node) ? node[segment] : void 0;
|
|
78
|
+
if (node !== void 0) notices.push({ key: path.join("."), message });
|
|
79
|
+
}
|
|
80
|
+
const permissions = document.permissions;
|
|
81
|
+
if (isRecord(permissions)) {
|
|
82
|
+
for (const list of PERMISSION_RULE_LISTS) {
|
|
83
|
+
const rules = permissions[list];
|
|
84
|
+
if (!Array.isArray(rules)) continue;
|
|
85
|
+
for (const rule of rules.filter(namesMemorySave)) {
|
|
86
|
+
notices.push({
|
|
87
|
+
key: `permissions.${list}`,
|
|
88
|
+
message: `The rule ${JSON.stringify(rule)} names MemorySave, which was removed: the model writes memory with Write, Edit and ApplyPatch now, so it matches nothing. To restrict memory writes, write a rule on those tools for the memory directory, or set memory.autoSave to false.`
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
57
93
|
if (document.compactStrategy !== void 0 && document.compactStrategy !== "summary") {
|
|
58
94
|
notices.push({
|
|
59
95
|
key: "compactStrategy",
|
|
@@ -89,4 +125,4 @@ export {
|
|
|
89
125
|
collectRemovedEnvNotices,
|
|
90
126
|
normalizeRemovedSettings
|
|
91
127
|
};
|
|
92
|
-
//# sourceMappingURL=chunk-
|
|
128
|
+
//# sourceMappingURL=chunk-HOZXKOE3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/settings-removed.ts"],"sourcesContent":["/**\n * Settings that used to exist and no longer do.\n *\n * Removing a feature leaves its configuration behind on real machines, and zod\n * strips unknown keys silently — so a user who configured a removed capability\n * gets no signal at all that their setting stopped being read. Worse, a removed\n * *value* of a surviving key (`compactStrategy: \"zero-mem\"`) fails the schema\n * and takes the whole document down with a raw validator dump naming no remedy.\n *\n * This module is the one place that knows what was removed, so both failure\n * modes become a sentence that names the feature, the file, and the edit to\n * make. Notices are advisory: a stale key must not brick a working install, so\n * nothing here throws. `book doctor` prints them, and the credential error\n * consults them when the removed block was the user's only credential.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\n\n/** A removed setting found in a document, and what to tell the user about it. */\nexport interface RemovedSettingNotice {\n /** Dotted path of the offending key, e.g. `auth` or `compactStrategy`. */\n key: string;\n message: string;\n}\n\n/** Removed top-level settings blocks, with the guidance for each. */\nconst REMOVED_BLOCKS: ReadonlyArray<readonly [string, string]> = [\n [\n 'auth',\n 'Subscription authentication was removed; Book authenticates with API keys only. ' +\n 'Delete the \"auth\" block, and set BOOK_API_KEY or provider.<id>.apiKey instead.',\n ],\n [\n 'harness',\n 'The adaptive harness was removed. Delete the \"harness\" block; it is no longer read.',\n ],\n [\n 'experimental',\n 'The experimental Zero-Mem capability was removed and with it the \"experimental\" block. ' +\n 'Delete it; compaction is always the production summary strategy now.',\n ],\n];\n\n/**\n * Removed keys inside a surviving block, by their path below the document root. The auto-memory\n * rework removed the approval inbox, the external-content quarantine and post-session extraction,\n * so the three settings that tuned them are no longer read.\n */\nconst REMOVED_NESTED: ReadonlyArray<readonly [readonly string[], string]> = [\n [\n ['memory', 'requireApproval'],\n 'The memory inbox was removed: the model writes memory files directly. Delete memory.requireApproval; ' +\n 'set memory.autoSave to false to make memory read-only.',\n ],\n [\n ['memory', 'quarantineExternal'],\n 'The external-content quarantine was removed: a session that read the web or an MCP server writes memory ' +\n 'like any other. Delete memory.quarantineExternal.',\n ],\n [\n ['memory', 'extraction'],\n 'Post-session memory extraction was removed. Delete the memory.extraction block; it is no longer read.',\n ],\n];\n\n/** The permission rule lists whose entries can name a tool that no longer exists. */\nconst PERMISSION_RULE_LISTS = ['allow', 'ask', 'deny'] as const;\n\n/** A permission rule that names the removed `MemorySave` tool: bare, or with a pattern. */\nfunction namesMemorySave(rule: unknown): rule is string {\n return typeof rule === 'string' && /^\\s*MemorySave\\s*(\\(|$)/.test(rule);\n}\n\n/** The credential file `book auth login` used to write, which nothing reads any more. */\nexport const REMOVED_AUTH_STORE_FILENAME = 'auth.json';\n\n/**\n * Guidance for the orphaned credential store. The file is deliberately not\n * deleted for the user: it is theirs, it is mode 0600, and a tool that removes\n * credentials without being asked is worse than one that reports them.\n */\nexport function removedAuthStoreNotice(path: string): string {\n return (\n `${path} holds an OAuth refresh token from the removed subscription login. ` +\n 'Nothing reads it any more — delete the file, and revoke the token with the provider ' +\n 'if it was ever used.'\n );\n}\n\n/** Environment variables that no longer do anything, with their guidance. */\nconst REMOVED_ENV_VARS: ReadonlyArray<readonly [string, string]> = [\n ['BOOK_AUTH_PROFILE', 'Subscription authentication was removed; use BOOK_API_KEY.'],\n ['BOOK_AUTH_CLIENT_ID', 'Subscription authentication was removed; use BOOK_API_KEY.'],\n ['BOOK_AUTH_CLIENT_SECRET', 'Subscription authentication was removed; use BOOK_API_KEY.'],\n ['BOOK_EXPERIMENTAL_ZERO_MEM', 'Zero-Mem was removed; summary compaction is the only strategy.'],\n ['BOOK_ZERO_MEM_MODEL_CACHE', 'Zero-Mem was removed.'],\n ['BOOK_ZERO_MEM_LOCAL_FILES_ONLY', 'Zero-Mem was removed.'],\n [\n 'BOOK_COMPACT_STRATEGY',\n 'Compaction strategy is no longer selectable; summary is the only one.',\n ],\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Parse a settings file for inspection, or undefined if it is missing or unreadable. */\nexport function readSettingsDocumentForNotices(path: string): unknown {\n try {\n if (!existsSync(path)) return undefined;\n return JSON.parse(readFileSync(path, 'utf-8'));\n } catch {\n // A malformed file is a different problem, reported by the loader.\n return undefined;\n }\n}\n\n/**\n * Did this machine carry subscription-auth configuration that is no longer read?\n *\n * Consulted only on the \"no credential\" error path, so the file reads cost\n * nothing in the normal case.\n */\nexport function hadRemovedAuthConfiguration(\n settingsFilePaths: readonly string[],\n env: NodeJS.ProcessEnv,\n): boolean {\n if (Object.keys(env).some((key) => key.startsWith('BOOK_AUTH_'))) return true;\n return settingsFilePaths.some((path) => {\n const document = readSettingsDocumentForNotices(path);\n return isRecord(document) && document.auth !== undefined;\n });\n}\n\n/**\n * Removed settings present in one parsed settings document.\n *\n * Takes the *raw* parsed JSON rather than a validated document, because the\n * whole point is to see the keys validation is about to discard.\n */\nexport function collectRemovedSettingNotices(document: unknown): RemovedSettingNotice[] {\n if (!isRecord(document)) return [];\n const notices: RemovedSettingNotice[] = [];\n for (const [key, message] of REMOVED_BLOCKS) {\n if (document[key] !== undefined) notices.push({ key, message });\n }\n for (const [path, message] of REMOVED_NESTED) {\n let node: unknown = document;\n for (const segment of path) node = isRecord(node) ? node[segment] : undefined;\n if (node !== undefined) notices.push({ key: path.join('.'), message });\n }\n const permissions = document.permissions;\n if (isRecord(permissions)) {\n for (const list of PERMISSION_RULE_LISTS) {\n const rules = permissions[list];\n if (!Array.isArray(rules)) continue;\n for (const rule of rules.filter(namesMemorySave)) {\n notices.push({\n key: `permissions.${list}`,\n message:\n `The rule ${JSON.stringify(rule)} names MemorySave, which was removed: the model writes memory with ` +\n 'Write, Edit and ApplyPatch now, so it matches nothing. To restrict memory writes, ' +\n 'write a rule on those tools for the memory directory, or set memory.autoSave to false.',\n });\n }\n }\n }\n // A removed *value* of a surviving key. `compactStrategy` still exists and is\n // now `\"summary\"` only, so the old Zero-Mem selector has to be named\n // explicitly or the user is left reading a schema error about a literal type.\n if (document.compactStrategy !== undefined && document.compactStrategy !== 'summary') {\n notices.push({\n key: 'compactStrategy',\n message:\n `compactStrategy: ${JSON.stringify(document.compactStrategy)} is no longer a choice. ` +\n 'Zero-Mem was removed and summary compaction is the only strategy; delete the key.',\n });\n }\n return notices;\n}\n\n/** Removed environment variables that are set in `env`, with their guidance. */\nexport function collectRemovedEnvNotices(env: NodeJS.ProcessEnv): RemovedSettingNotice[] {\n const notices: RemovedSettingNotice[] = [];\n for (const [name, message] of REMOVED_ENV_VARS) {\n // The client-id variables are per-profile suffixed, so match by prefix.\n const matches =\n name.endsWith('CLIENT_ID') || name.endsWith('CLIENT_SECRET')\n ? Object.keys(env).filter((key) => key.startsWith(`${name}_`) || key === name)\n : env[name] !== undefined\n ? [name]\n : [];\n for (const key of matches) notices.push({ key, message });\n }\n return notices;\n}\n\n/**\n * Drop removed keys and coerce removed values so a stale document still loads.\n *\n * Unknown blocks are stripped by validation anyway; the value coercion is the\n * part that matters, because without it one obsolete string fails the whole\n * file and Book will not start. Returns a copy — the caller's document is left\n * alone so it can still be reported on.\n */\nexport function normalizeRemovedSettings(document: unknown): unknown {\n if (!isRecord(document)) return document;\n if (document.compactStrategy === undefined || document.compactStrategy === 'summary') {\n return document;\n }\n const normalized = { ...document };\n delete normalized.compactStrategy;\n return normalized;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveBookHome
|
|
3
|
+
} from "./chunk-ZPSZZUWF.js";
|
|
4
|
+
|
|
5
|
+
// src/workspace-files.ts
|
|
6
|
+
import { lstatSync } from "fs";
|
|
7
|
+
import { basename, dirname, join, resolve } from "path";
|
|
8
|
+
function safeMessage(error) {
|
|
9
|
+
return error instanceof Error ? error.message : String(error);
|
|
10
|
+
}
|
|
11
|
+
function projectFileWriteRefusal(path) {
|
|
12
|
+
const target = resolve(path);
|
|
13
|
+
const home = resolve(resolveBookHome());
|
|
14
|
+
if (target === join(home, "settings.json") || target === join(home, "mcp.json")) {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const folder = dirname(target);
|
|
19
|
+
if (basename(folder) === ".book" && folder !== home) {
|
|
20
|
+
const folderStats = lstatSync(folder, { throwIfNoEntry: false });
|
|
21
|
+
if (folderStats && !folderStats.isDirectory()) {
|
|
22
|
+
return "its .book folder is not a regular directory, so Book left it unchanged";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const stats = lstatSync(target, { throwIfNoEntry: false });
|
|
26
|
+
if (!stats || stats.isFile()) return void 0;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return `could not be inspected (${safeMessage(error)}), so Book left it unchanged`;
|
|
29
|
+
}
|
|
30
|
+
return "not a regular file, so Book left it unchanged";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
projectFileWriteRefusal
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=chunk-IDYWN37T.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workspace-files.ts"],"sourcesContent":["/**\n * The rule for the files Book itself writes into a workspace. Its own module, with nothing but\n * `fs`, `path` and `BOOK_HOME` behind it, so the debug logger can use it too.\n */\nimport { lstatSync } from 'fs';\nimport { basename, dirname, join, resolve } from 'path';\nimport { resolveBookHome } from './book-home.js';\n\nfunction safeMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Why Book will not write the workspace file at `path`, or undefined when it may: the one check\n * every one of Book's own workspace writes goes through (`.book/settings.json`,\n * `.book/settings.local.json`, `.book/migrations.json`, a project `.mcp.json`, `/export`, the\n * default `.book/debug.log`), including the read a write starts from.\n *\n * Such a file is written only while it is a regular file or does not exist yet, in a `.book`\n * folder that is a real directory or does not exist yet. Anything else (a link, a FIFO, a device,\n * a directory in the file's place) is left exactly as it is: not read for the write, not written\n * through, and not replaced. The answer is the reason, without the path or a full stop, for the\n * caller's one-line notice: `<path>: <reason>`.\n *\n * The user-global files (`<BOOK_HOME>/settings.json` and `<BOOK_HOME>/mcp.json`) keep their\n * behaviour: they are the user's own, and one kept elsewhere and linked into place is a setup this\n * must not break. Only those two paths are exempt, not the rest of `<BOOK_HOME>`, which also holds\n * checkouts of other repositories.\n */\nexport function projectFileWriteRefusal(path: string): string | undefined {\n const target = resolve(path);\n const home = resolve(resolveBookHome());\n if (target === join(home, 'settings.json') || target === join(home, 'mcp.json')) {\n return undefined;\n }\n try {\n const folder = dirname(target);\n // `<BOOK_HOME>` itself (a workspace at `$HOME`, where `.book` is it) is the user's own folder,\n // and a dotfiles setup often links it into place.\n if (basename(folder) === '.book' && folder !== home) {\n const folderStats = lstatSync(folder, { throwIfNoEntry: false });\n if (folderStats && !folderStats.isDirectory()) {\n return 'its .book folder is not a regular directory, so Book left it unchanged';\n }\n }\n const stats = lstatSync(target, { throwIfNoEntry: false });\n if (!stats || stats.isFile()) return undefined;\n } catch (error) {\n return `could not be inspected (${safeMessage(error)}), so Book left it unchanged`;\n }\n return 'not a regular file, so Book left it unchanged';\n}\n"],"mappings":";;;;;AAIA,SAAS,iBAAiB;AAC1B,SAAS,UAAU,SAAS,MAAM,eAAe;AAGjD,SAAS,YAAY,OAAwB;AAC3C,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAmBO,SAAS,wBAAwB,MAAkC;AACxE,QAAM,SAAS,QAAQ,IAAI;AAC3B,QAAM,OAAO,QAAQ,gBAAgB,CAAC;AACtC,MAAI,WAAW,KAAK,MAAM,eAAe,KAAK,WAAW,KAAK,MAAM,UAAU,GAAG;AAC/E,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,SAAS,QAAQ,MAAM;AAG7B,QAAI,SAAS,MAAM,MAAM,WAAW,WAAW,MAAM;AACnD,YAAM,cAAc,UAAU,QAAQ,EAAE,gBAAgB,MAAM,CAAC;AAC/D,UAAI,eAAe,CAAC,YAAY,YAAY,GAAG;AAC7C,eAAO;AAAA,MACT;AAAA,IACF;AACA,UAAM,QAAQ,UAAU,QAAQ,EAAE,gBAAgB,MAAM,CAAC;AACzD,QAAI,CAAC,SAAS,MAAM,OAAO,EAAG,QAAO;AAAA,EACvC,SAAS,OAAO;AACd,WAAO,2BAA2B,YAAY,KAAK,CAAC;AAAA,EACtD;AACA,SAAO;AACT;","names":[]}
|