@khanglvm/outline-cli 0.1.5 → 0.1.6

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.6 - 2026-03-08
4
+
5
+ - Changes since `v0.1.5`.
6
+ - build(npm): whitelist published files (b2838fa)
7
+
3
8
  ## 0.1.5 - 2026-03-08
4
9
 
5
10
  - Changes since `v0.1.4`.
package/package.json CHANGED
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "name": "@khanglvm/outline-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Agent-optimized CLI for Outline API",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "outline-cli": "bin/outline-cli.js",
8
8
  "outline-agent": "bin/outline-cli.js"
9
9
  },
10
+ "files": [
11
+ "bin/",
12
+ "src/",
13
+ "README.md",
14
+ "CHANGELOG.md"
15
+ ],
10
16
  "scripts": {
11
17
  "start": "node ./bin/outline-cli.js",
12
18
  "check": "node ./bin/outline-cli.js --help",
@@ -4,7 +4,7 @@ export const ENTRY_INTEGRITY_MANIFEST = Object.freeze({
4
4
  algorithm: "sha256",
5
5
  signatureAlgorithm: "sha256-salted-manifest-v1",
6
6
  signature: "c69d41948b78c8ae35c0223377149ece8df5588863a010adf92a0b6ad11876d3",
7
- generatedAt: "2026-03-08T01:17:47.858Z",
7
+ generatedAt: "2026-03-08T04:29:38.219Z",
8
8
  files: [
9
9
  {
10
10
  "path": "src/action-gate.js",
package/.env.test.example DELETED
@@ -1,2 +0,0 @@
1
- OUTLINE_TEST_BASE_URL=https://example.getoutline.site
2
- OUTLINE_TEST_API_KEY=ol_api_replace_me
package/AGENTS.md DELETED
@@ -1,107 +0,0 @@
1
- # AGENTS.md
2
-
3
- ## Scope
4
- This repository contains `outline-cli` (`outline-agent` alias), a Node.js CLI optimized for AI agents to interact with Outline via real API calls.
5
-
6
- ## Core Principles
7
- - Keep outputs deterministic and machine-readable.
8
- - Optimize for low-token workflows (`summary/ids` views first, hydrate later).
9
- - Prefer batch operations for fewer round trips.
10
- - Never mutate existing workspace content in tests except suite-created test documents.
11
- - Keep security strict: no hardcoded live secrets in tracked files.
12
-
13
- ## Runtime + Commands
14
- - Node.js: `>=18.17`
15
- - Install: `npm install`
16
- - Basic check: `npm run check`
17
- - Full tests (real environment): `npm test`
18
-
19
- ## Repository Map
20
- - CLI entrypoint: `bin/outline-cli.js`
21
- - Command wiring/output modes: `src/cli.js`
22
- - API client/auth/retry: `src/outline-client.js`
23
- - Tool registry/core tools: `src/tools.js`
24
- - Navigation/search tools: `src/tools.navigation.js`
25
- - Mutation/revision tools: `src/tools.mutation.js`
26
- - Platform/cleanup/capabilities tools: `src/tools.platform.js`
27
- - Tool arg validation: `src/tool-arg-schemas.js`
28
- - Live integration tests: `test/live.integration.test.js`
29
-
30
- ## Local Environment
31
- - Template: `.env.test.example`
32
- - Local secret file (untracked): `.env.test.local`
33
- - Required vars for live tests:
34
- - `OUTLINE_TEST_BASE_URL`
35
- - `OUTLINE_TEST_API_KEY`
36
-
37
- ## Development Workflow
38
- 1. Pull latest branch and inspect current tool contracts:
39
- - `node ./bin/outline-cli.js tools contract all --result-mode inline`
40
- 2. Refresh raw API method inventory from prior sessions / wrappers, then diff wrapped vs raw:
41
- - `rg -o 'client\\.call\\(\"[^\"]+\"' src | sed -E 's/.*\\(\"//; s/\"$//' | sort | uniq`
42
- - record missing high-value endpoints in `/tmp/knowledges/outline-raw-api-gap.md`
43
- 3. Implement minimal, compatible changes (preserve response envelopes).
44
- 4. Add/adjust arg schema in `src/tool-arg-schemas.js` for every new tool arg.
45
- 5. Add/adjust real integration tests in `test/live.integration.test.js`.
46
- 6. Run:
47
- - `npm run check`
48
- - `npm test`
49
- 7. Update docs when behavior/signature changes:
50
- - `README.md`
51
- - `docs/TOOL_CONTRACTS.md`
52
-
53
- ## Testing Rules (Live Env)
54
- - No mocks when endpoint can be exercised live.
55
- - Mutation tests must:
56
- - create a dedicated test doc first,
57
- - perform all edits/patch/revision operations on that doc,
58
- - delete it in cleanup.
59
- - Read-only tools (search/list/info) may use site-wide data.
60
- - Keep tests resilient: isolate steps with subtests and clear assertions.
61
-
62
- ## Tool/Output Requirements
63
- - Default JSON output remains stable.
64
- - `--output ndjson` must stay stream-friendly and compatible with file-offload behavior.
65
- - Large responses should offload to temp files when `result-mode` is `auto|file`.
66
- - `batch` should default to token-efficient item payloads; full envelopes opt-in.
67
-
68
- ## Agent Action Gate
69
- - Mutating actions are gated by default and must be explicit:
70
- - pass `performAction: true` on mutation/delete operations.
71
- - Safe delete flow requires prior read confirmation:
72
- 1. Call `documents.info` with `armDelete: true` on target document(s).
73
- 2. Use returned `deleteReadReceipt.token` as `readToken` for delete.
74
- 3. Execute delete only with `performAction: true`.
75
- - Delete operations must fail if read token is missing, stale, mismatched, or expired.
76
-
77
- ## Security + Secrets
78
- - Never commit real Outline API keys or `.env.test.local`.
79
- - Secret scan guard test must pass.
80
- - If a key is exposed, rotate immediately and update local env.
81
-
82
- ## Deployment / Release
83
- - Pre-release checklist:
84
- 1. Ensure clean git working tree.
85
- 2. Ensure `OUTLINE_ENTRY_BUILD_KEY` is set (`.env.local` or env).
86
- 3. Ensure npm auth is ready (`npm login`).
87
- - Primary workflow command:
88
- - `npm run release -- --bump patch`
89
- - or `npm run release -- --version X.Y.Z`
90
- - Prepare-only workflow (no publish/push):
91
- - `npm run release:prepare -- --bump patch`
92
- - Agent execution rule:
93
- - If user asks to "deploy" or "release", run `npm run release -- --bump patch` by default unless user specifies a version/tag strategy.
94
- - Release script responsibilities (`scripts/release.mjs`):
95
- - version bump (`npm version --no-git-tag-version`)
96
- - changelog update (`CHANGELOG.md`)
97
- - integrity refresh (`npm run integrity:refresh`)
98
- - verification (`npm run check`, `npm test`)
99
- - packaging validation (`npm pack --dry-run`)
100
- - git commit + tag (`chore(release): vX.Y.Z`, `vX.Y.Z`)
101
- - npm publish (`--access public`) and git push to `origin`
102
-
103
- ## Temporary Knowledge Notes
104
- Use local scratch references under:
105
- - `/tmp/knowledges/*.md`
106
-
107
- These are intentionally ignored by git and can store short-lived research/decision notes.
@@ -1,8 +0,0 @@
1
- # Tool Contracts
2
-
3
- Generated from `node ./bin/outline-cli.js tools contract all --result-mode inline`.
4
-
5
- ```json
6
- {"ok":true,"contract":[{"name":"api.call","signature":"api.call(args: { method?: string; endpoint?: string; body?: object; includePolicies?: boolean; maxAttempts?: number; select?: string[]; performAction?: boolean; readToken?: string })","description":"Call any Outline API RPC endpoint directly.","usageExample":{"tool":"api.call","args":{"method":"documents.info","body":{"id":"outline-api-NTpezNwhUP"}}},"bestPractices":["Use this for endpoints not yet wrapped as dedicated tools.","Send only the fields you need in `body` and use `select` to reduce tokens.","Pass either `method` or `endpoint`; both are accepted aliases.","Set maxAttempts=2 for read endpoints to absorb transient 429/5xx.","Mutating methods are action-gated; set performAction=true intentionally."]},{"name":"auth.info","signature":"auth.info(args?: { includePolicies?: boolean; view?: 'summary' | 'full' })","description":"Return authenticated user and team info to confirm profile permissions.","usageExample":{"tool":"auth.info","args":{"view":"summary"}},"bestPractices":["Call this once per session before mutating data.","Use summary view first, then full only when needed.","Inspect policies only when making capability decisions."]},{"name":"documents.search","signature":"documents.search(args: { query?: string; queries?: string[]; mode?: 'semantic' | 'titles'; limit?: number; offset?: number; collectionId?: string; documentId?: string; userId?: string; statusFilter?: string[]; dateFilter?: 'day'|'week'|'month'|'year'; snippetMinWords?: number; snippetMaxWords?: number; sort?: string; direction?: 'ASC'|'DESC'; view?: 'summary'|'ids'|'full'; includePolicies?: boolean; merge?: boolean; concurrency?: number; })","description":"Search documents with single or multi-query batch in one invocation.","usageExample":{"tool":"documents.search","args":{"queries":["deployment runbook","oncall escalation"],"mode":"semantic","limit":8,"view":"summary","merge":true}},"bestPractices":["Prefer `queries[]` batch mode to reduce round trips.","Use `view=ids` for planning and follow with documents.info only on selected IDs.","Tune snippetMinWords/snippetMaxWords to control context window size."]},{"name":"documents.list","signature":"documents.list(args?: { limit?: number; offset?: number; sort?: string; direction?: 'ASC'|'DESC'; collectionId?: string; parentDocumentId?: string | null; rootOnly?: boolean; userId?: string; statusFilter?: string[]; view?: 'ids'|'summary'|'full'; includePolicies?: boolean })","description":"List documents with filtering and pagination.","usageExample":{"tool":"documents.list","args":{"collectionId":"6f35e6db-5930-4db8-9c31-66fe12f9f4aa","limit":20,"statusFilter":["published"],"view":"summary"}},"bestPractices":["Use small page sizes (10-25) and iterate with offset.","Use rootOnly=true (or parentDocumentId=null) to list only collection root pages.","Use summary view unless the full document body is required."]},{"name":"documents.info","signature":"documents.info(args: { id?: string; ids?: string[]; shareId?: string; view?: 'summary'|'full'; includePolicies?: boolean; concurrency?: number; armDelete?: boolean; readTokenTtlSeconds?: number })","description":"Read one or many documents by ID.","usageExample":{"tool":"documents.info","args":{"ids":["doc-1","doc-2","doc-3"],"view":"summary","concurrency":3}},"bestPractices":["Use `ids[]` batch mode to fetch multiple docs in one CLI call.","Use summary view first; only request full for final chosen docs.","Handle partial failures by checking each item.ok in batched result.","Set armDelete=true when you need a short-lived delete read token for a safe delete flow."]},{"name":"documents.create","signature":"documents.create(args: { title?: string; text?: string; collectionId?: string; parentDocumentId?: string; publish?: boolean; icon?: string; color?: string; templateId?: string; fullWidth?: boolean; view?: 'summary'|'full' })","description":"Create a new document in Outline.","usageExample":{"tool":"documents.create","args":{"title":"Incident 2026-03-04","text":"# Incident\n\nSummary...","collectionId":"collection-id","publish":true}},"bestPractices":["Set publish=true only when collection/parent is known.","Use templates when available to standardize structure.","Store long markdown in a file and pass via args-file to avoid shell escaping issues."]},{"name":"documents.update","signature":"documents.update(args: { id: string; title?: string; text?: string; editMode?: 'replace'|'append'|'prepend'; publish?: boolean; collectionId?: string; templateId?: string; fullWidth?: boolean; insightsEnabled?: boolean; view?: 'summary'|'full'; performAction?: boolean })","description":"Update an existing document.","usageExample":{"tool":"documents.update","args":{"id":"doc-id","text":"\n\n## Follow-up\n- Added RCA","editMode":"append"}},"bestPractices":["For append/prepend, include only incremental text instead of full document body.","Read the document first when multiple agents may edit concurrently.","Use publish=true only when transitioning drafts to published state intentionally.","This tool is action-gated; set performAction=true only after explicit confirmation."]},{"name":"collections.list","signature":"collections.list(args?: { query?: string; limit?: number; offset?: number; sort?: string; direction?: 'ASC'|'DESC'; statusFilter?: string[]; view?: 'summary'|'full'; includePolicies?: boolean })","description":"List collections visible to the current profile.","usageExample":{"tool":"collections.list","args":{"query":"engineering","limit":10,"view":"summary"}},"bestPractices":["Resolve collection IDs once and reuse them in later calls.","Use summary view for navigation and planning.","Include policies only when checking write privileges."]},{"name":"collections.info","signature":"collections.info(args: { id?: string; ids?: string[]; view?: 'summary'|'full'; includePolicies?: boolean; concurrency?: number })","description":"Read one or many collections by ID.","usageExample":{"tool":"collections.info","args":{"ids":["col-1","col-2"],"view":"summary"}},"bestPractices":["Use ids[] to batch collection hydration in one request.","Treat missing collections as permission or existence issues.","Use full view only for collection metadata you actually need."]},{"name":"collections.create","signature":"collections.create(args: { name: string; description?: string; permission?: string; icon?: string; color?: string; sharing?: boolean; view?: 'summary'|'full' })","description":"Create a collection.","usageExample":{"tool":"collections.create","args":{"name":"Agent Notes","description":"Working area for AI-assisted drafts","permission":"read_write","sharing":false}},"bestPractices":["Prefer explicit permission values to avoid ambiguous defaults.","Create collection first, then create documents under it.","Use summary output in autonomous loops."]},{"name":"collections.update","signature":"collections.update(args: { id: string; name?: string; description?: string; permission?: string; icon?: string; color?: string; sharing?: boolean; view?: 'summary'|'full'; performAction?: boolean })","description":"Update collection metadata.","usageExample":{"tool":"collections.update","args":{"id":"col-id","description":"Updated description","sharing":true}},"bestPractices":["Read collection first when coordinating changes across agents.","Apply minimal field diffs rather than resending all properties.","Keep sharing changes explicit and auditable.","This tool is action-gated; set performAction=true only after explicit confirmation."]},{"name":"documents.resolve","signature":"documents.resolve(args: { query?: string; queries?: string[]; collectionId?: string; limit?: number; strict?: boolean; strictThreshold?: number; view?: 'ids'|'summary'|'full'; concurrency?: number; })","description":"Resolve fuzzy document references by combining title search with semantic fallback and returning confidence-ranked candidates.","usageExample":{"tool":"documents.resolve","args":{"queries":["incident handbook","oncall escalation"],"limit":6,"view":"summary"}},"bestPractices":["Use `strict=true` when only near-exact matches should be auto-selected.","Start with `view=ids` in planner loops, then hydrate selected IDs separately.","Send multiple references in `queries[]` to reduce tool round trips."]},{"name":"documents.resolve_urls","signature":"documents.resolve_urls(args: { url?: string; urls?: string[]; collectionId?: string; limit?: number; strict?: boolean; strictHost?: boolean; strictThreshold?: number; view?: 'ids'|'summary'|'full'; concurrency?: number; snippetMinWords?: number; snippetMaxWords?: number; excerptChars?: number; forceGroupedResult?: boolean; maxAttempts?: number; })","description":"Resolve document URLs (doc/share links) into confidence-ranked document candidates with URL-id/host-aware boosts.","usageExample":{"tool":"documents.resolve_urls","args":{"urls":["https://handbook.example.com/doc/event-tracking-data-A7hLXuHZJl","https://handbook.example.com/doc/campaign-detail-page-GWK1uA8w35#d-GWK1uA8w35"],"strict":true,"strictThreshold":0.85,"view":"summary"}},"bestPractices":["Use strictHost=true when links should belong to the currently selected profile host only.","Use strict=true for automation paths that should avoid weak URL matches.","Start with view=ids, then hydrate selected IDs with documents.info for low-token loops."]},{"name":"documents.canonicalize_candidates","signature":"documents.canonicalize_candidates(args: { query?: string; queries?: string[]; ids?: string[]; collectionId?: string; limit?: number; strict?: boolean; strictThreshold?: number; titleSimilarityThreshold?: number; view?: 'ids'|'summary'|'full'; concurrency?: number; hydrateConcurrency?: number; snippetMinWords?: number; snippetMaxWords?: number; excerptChars?: number; maxAttempts?: number; })","description":"Canonicalize noisy/duplicate candidate sets into stable clusters with one preferred canonical document per cluster.","usageExample":{"tool":"documents.canonicalize_candidates","args":{"queries":["campaign detail","campaign tracking event"],"strict":true,"titleSimilarityThreshold":0.8,"view":"summary"}},"bestPractices":["Feed this tool with multi-query retrieval inputs before answer generation to reduce duplicate/noisy context.","Use strict=true + strictThreshold when low-confidence matches should be dropped from canonical clusters.","Inspect duplicateIds/memberCount to detect ambiguous sources before applying changes."]},{"name":"collections.tree","signature":"collections.tree(args: { collectionId: string; includeDrafts?: boolean; maxDepth?: number; view?: 'summary'|'full'; pageSize?: number; maxPages?: number; })","description":"Build a parent/child document tree for a collection without modifying server data.","usageExample":{"tool":"collections.tree","args":{"collectionId":"collection-id","includeDrafts":false,"maxDepth":4,"view":"summary"}},"bestPractices":["Keep `view=summary` and low `maxDepth` for navigation tasks to save tokens.","Set includeDrafts=true only if draft pages matter for your workflow.","Use output tree IDs as anchors for targeted `documents.info` calls."]},{"name":"search.expand","signature":"search.expand(args: { query?: string; queries?: string[]; mode?: 'semantic'|'titles'; limit?: number; expandLimit?: number; view?: 'ids'|'summary'|'full'; concurrency?: number; hydrateConcurrency?: number; })","description":"Search and then hydrate top-ranked documents in one call, returning compact joined search+document output.","usageExample":{"tool":"search.expand","args":{"query":"postmortem template","mode":"semantic","limit":8,"expandLimit":3,"view":"summary"}},"bestPractices":["Use low `expandLimit` (2-5) to minimize payload while preserving answer quality.","Use `queries[]` for multi-intent retrieval in one request.","For multi-query runs, duplicate document hydration is automatically cached within the same tool call.","Prefer `view=summary` unless a full markdown body is strictly needed."]},{"name":"search.research","signature":"search.research(args: { question?: string; query?: string; queries?: string[]; collectionId?: string; limitPerQuery?: number; offset?: number; includeTitleSearch?: boolean; includeSemanticSearch?: boolean; precisionMode?: 'balanced'|'precision'|'recall'; minScore?: number; diversify?: boolean; diversityLambda?: number; rrfK?: number; expandLimit?: number; maxDocuments?: number; seenIds?: string[]; view?: 'ids'|'summary'|'full'; perQueryView?: 'ids'|'summary'|'full'; perQueryHitLimit?: number; evidencePerDocument?: number; suggestedQueryLimit?: number; includePerQuery?: boolean; includeExpanded?: boolean; includeCoverage?: boolean; includeBacklinks?: boolean; backlinksLimit?: number; backlinksConcurrency?: number; concurrency?: number; hydrateConcurrency?: number; contextChars?: number; excerptChars?: number; maxAttempts?: number; })","description":"Run multi-query, multi-source research retrieval with weighted reranking, optional diversification, hydration, and follow-up cursor support for multi-turn QA.","usageExample":{"tool":"search.research","args":{"question":"How do we run incident communication and escalation?","queries":["incident comms","escalation matrix"],"includeTitleSearch":true,"includeSemanticSearch":true,"precisionMode":"precision","limitPerQuery":8,"perQueryHitLimit":4,"evidencePerDocument":3,"expandLimit":5,"includeBacklinks":true,"backlinksLimit":3,"view":"summary"}},"bestPractices":["Pass prior `next.seenIds` into `seenIds` for follow-up turns to avoid repetition.","Use `precisionMode=precision` for answer-grade retrieval and `precisionMode=recall` for exploration.","Set `perQueryView=ids` + `perQueryHitLimit` to reduce token cost while preserving traceability.","Enable `includeBacklinks` when one-call context gathering is more important than raw latency.","Keep `expandLimit` small and raise only when answer confidence is insufficient."]},{"name":"documents.safe_update","signature":"documents.safe_update(args: { id: string; expectedRevision: number; title?: string; text?: string; editMode?: 'replace'|'append'|'prepend'; icon?: string; color?: string; fullWidth?: boolean; templateId?: string; collectionId?: string; insightsEnabled?: boolean; publish?: boolean; dataAttributes?: any[]; view?: 'summary'|'full'; performAction?: boolean })","description":"Update document only if current revision matches expectedRevision.","usageExample":{"tool":"documents.safe_update","args":{"id":"doc-id","expectedRevision":3,"text":"\n\n## Changes\n- added new action","editMode":"append"}},"bestPractices":["Read document first and pass returned revision as expectedRevision.","Handle revision_conflict deterministically and re-read before retry.","Use append/prepend for low-token incremental writes.","This tool is action-gated; set performAction=true only after explicit confirmation."]},{"name":"documents.diff","signature":"documents.diff(args: { id: string; proposedText: string; includeFullHunks?: boolean; hunkLimit?: number; hunkLineLimit?: number })","description":"Compute line-level diff between current document text and proposed text.","usageExample":{"tool":"documents.diff","args":{"id":"doc-id","proposedText":"# Title\n\nUpdated body"}},"bestPractices":["Run diff before patch/apply to reduce accidental destructive edits.","Use preview hunks first; request full hunks only when needed.","Track added/removed counts to detect large unintended changes."]},{"name":"documents.apply_patch","signature":"documents.apply_patch(args: { id: string; patch: string; mode?: 'unified'|'replace'; expectedRevision?: number; title?: string; view?: 'summary'|'full'; performAction?: boolean })","description":"Apply unified diff patch (or full replace) to a document and persist update.","usageExample":{"tool":"documents.apply_patch","args":{"id":"doc-id","expectedRevision":7,"mode":"unified","patch":"@@ -1,1 +1,1 @@\n-Old\n+New"}},"bestPractices":["Prefer unified mode for minimal, auditable text changes.","Pass expectedRevision when coordinating concurrent editors to prevent stale writes.","On patch_apply_failed, re-read latest text and regenerate patch.","Use replace mode only for full-document rewrites.","This tool is action-gated; set performAction=true only after explicit confirmation."]},{"name":"documents.apply_patch_safe","signature":"documents.apply_patch_safe(args: { id: string; expectedRevision: number; patch: string; mode?: 'unified'|'replace'; title?: string; view?: 'summary'|'full'; performAction?: boolean })","description":"Apply patch with mandatory revision guard so writes only proceed from the expected document revision.","usageExample":{"tool":"documents.apply_patch_safe","args":{"id":"doc-id","expectedRevision":7,"mode":"unified","patch":"@@ -1,1 +1,1 @@\n-Old\n+New"}},"bestPractices":["Use this wrapper for all automated patch writes to enforce optimistic concurrency.","Re-read the document and regenerate patch when revision_conflict is returned.","Keep unified mode for minimal, auditable edits; use replace mode for full rewrites only.","This tool is action-gated; set performAction=true only after explicit confirmation."]},{"name":"documents.batch_update","signature":"documents.batch_update(args: { updates: Array<{ id: string; expectedRevision?: number; title?: string; text?: string; editMode?: 'replace'|'append'|'prepend' }>; concurrency?: number; continueOnError?: boolean; performAction?: boolean })","description":"Run multiple document updates in one call with per-item results.","usageExample":{"tool":"documents.batch_update","args":{"updates":[{"id":"doc-1","text":"\n\nupdate one","editMode":"append"},{"id":"doc-2","title":"Renamed"}],"concurrency":2,"continueOnError":true}},"bestPractices":["Use expectedRevision per update for multi-agent safety.","Set continueOnError=false for transactional-style stop-on-first-failure flows.","Use per-item results to retry only failed updates.","This tool is action-gated; set performAction=true only after explicit confirmation."]},{"name":"documents.delete","signature":"documents.delete(args: { id: string; readToken: string; performAction?: boolean; maxAttempts?: number })","description":"Delete a document with mandatory read-confirmation token and action gate.","usageExample":{"tool":"documents.delete","args":{"id":"doc-id","readToken":"<token from documents.info armDelete=true>","performAction":true}},"bestPractices":["Always read with documents.info armDelete=true immediately before delete.","Delete tokens are profile-bound and revision-sensitive; re-read on stale token errors.","Keep performAction=false by default in automation and set true only for the final confirmed call."]},{"name":"documents.plan_batch_update","signature":"documents.plan_batch_update(args: { id?: string; ids?: string[]; query?: string; queries?: string[]; collectionId?: string; rules?: Array<{ field?: 'title'|'text'|'both'; find: string; replace?: string; caseSensitive?: boolean; wholeWord?: boolean; all?: boolean }>; includeTitleSearch?: boolean; includeSemanticSearch?: boolean; limitPerQuery?: number; offset?: number; maxDocuments?: number; readConcurrency?: number; includeUnchanged?: boolean; hunkLimit?: number; hunkLineLimit?: number; maxAttempts?: number; })","description":"Plan multi-document refactors/renames by previewing affected docs, replacement counts, and text hunks before applying.","usageExample":{"tool":"documents.plan_batch_update","args":{"query":"incident communication","rules":[{"field":"both","find":"SEV1","replace":"SEV-1","wholeWord":true}],"maxDocuments":20}},"bestPractices":["Review `impacts` with the user before applying changes.","Use precise rules (wholeWord/caseSensitive) to avoid broad unintended edits.","Apply with `documents.apply_batch_plan` using the returned `planHash` for explicit confirmation."]},{"name":"documents.plan_terminology_refactor","signature":"documents.plan_terminology_refactor(args: { glossary?: Array<{ from?: string; to?: string; find?: string; replace?: string; field?: 'title'|'text'|'both'; caseSensitive?: boolean; wholeWord?: boolean; all?: boolean }>; map?: Record<string, string>; glossaryMap?: Record<string, string>; terminologyMap?: Record<string, string>; id?: string; ids?: string[]; query?: string; queries?: string[]; collectionId?: string; includeTitleSearch?: boolean; includeSemanticSearch?: boolean; limitPerQuery?: number; offset?: number; maxDocuments?: number; readConcurrency?: number; includeUnchanged?: boolean; hunkLimit?: number; hunkLineLimit?: number; maxAttempts?: number })","description":"Plan terminology refactors using glossary/map inputs and return plan_batch_update-compatible output with metadata.","usageExample":{"tool":"documents.plan_terminology_refactor","args":{"queries":["incident response","escalation policy"],"glossary":[{"from":"SEV1","to":"SEV-1","field":"both","wholeWord":true},{"from":"on call","to":"on-call","field":"text"}],"maxDocuments":25}},"bestPractices":["Use glossary[] when each mapping needs its own field/casing controls.","Use map for fast one-to-one terminology upgrades across both title and text.","Review returned impacts/planHash before applying with documents.apply_batch_plan."]},{"name":"documents.apply_batch_plan","signature":"documents.apply_batch_plan(args: { plan: object; confirmHash: string; dryRun?: boolean; continueOnError?: boolean; concurrency?: number; view?: 'summary'|'full'; maxAttempts?: number; performAction?: boolean; })","description":"Apply a previously generated batch-update plan with hash confirmation and revision-safe updates.","usageExample":{"tool":"documents.apply_batch_plan","args":{"confirmHash":"sha256-hash-from-plan","plan":{"version":1,"items":[{"id":"doc-id","expectedRevision":12,"title":"Renamed title"}]}}},"bestPractices":["Require explicit user confirmation of `planHash` before execution.","Keep `dryRun=true` for one final verification step in automation loops.","Treat `revision_conflict` results as a re-plan signal, not an auto-retry.","When dryRun=false this tool is action-gated; set performAction=true only after explicit confirmation."]},{"name":"revisions.list","signature":"revisions.list(args: { documentId: string; limit?: number; offset?: number; sort?: string; direction?: 'ASC'|'DESC'; view?: 'summary'|'full' })","description":"List revisions for a document.","usageExample":{"tool":"revisions.list","args":{"documentId":"doc-id","limit":10,"view":"summary"}},"bestPractices":["Use small limits and paginate for long histories.","Capture revision IDs before performing restore operations.","Use summary view for fast planning loops."]},{"name":"revisions.diff","signature":"revisions.diff(args: { id: string; baseRevisionId: string; targetRevisionId: string; includeFullHunks?: boolean; hunkLimit?: number; hunkLineLimit?: number; view?: 'summary'|'full'; maxAttempts?: number })","description":"Compute line-level diff between two revisions by hydrating both with revisions.info.","usageExample":{"tool":"revisions.diff","args":{"id":"doc-id","baseRevisionId":"revision-id-older","targetRevisionId":"revision-id-newer","view":"summary"}},"bestPractices":["Pass adjacent revisions first to isolate rollback root causes.","Use preview hunks first; set includeFullHunks=true only when needed.","Verify revision metadata before applying restore or patch actions."]},{"name":"revisions.restore","signature":"revisions.restore(args: { id: string; revisionId: string; collectionId?: string; view?: 'summary'|'full'; performAction?: boolean })","description":"Restore a document to a specific revision using documents.restore endpoint.","usageExample":{"tool":"revisions.restore","args":{"id":"doc-id","revisionId":"revision-id"}},"bestPractices":["List revisions first and confirm target revision id.","Use on test/sandbox docs before applying to important docs.","Capture post-restore revision for subsequent safe updates.","This tool is action-gated; set performAction=true only after explicit confirmation."]},{"name":"shares.list","signature":"shares.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List shares.","usageExample":{"tool":"shares.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"shares.info","signature":"shares.info(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Get share details.","usageExample":{"tool":"shares.info","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"shares.create","signature":"shares.create(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Create a share.","usageExample":{"tool":"shares.create","args":{"documentId":"document-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"shares.update","signature":"shares.update(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Update a share.","usageExample":{"tool":"shares.update","args":{"id":"share-id","published":true,"performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"shares.revoke","signature":"shares.revoke(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Revoke a share.","usageExample":{"tool":"shares.revoke","args":{"id":"share-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"templates.list","signature":"templates.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List templates.","usageExample":{"tool":"templates.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"templates.info","signature":"templates.info(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Get template details.","usageExample":{"tool":"templates.info","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"templates.create","signature":"templates.create(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Create a template.","usageExample":{"tool":"templates.create","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"templates.update","signature":"templates.update(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Update a template.","usageExample":{"tool":"templates.update","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"templates.delete","signature":"templates.delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Delete a template.","usageExample":{"tool":"templates.delete","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"templates.restore","signature":"templates.restore(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Restore a template.","usageExample":{"tool":"templates.restore","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"templates.duplicate","signature":"templates.duplicate(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Duplicate a template.","usageExample":{"tool":"templates.duplicate","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"documents.templatize","signature":"documents.templatize(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Convert a document into a template.","usageExample":{"tool":"documents.templatize","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"documents.import","signature":"documents.import(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Import a document from JSON payload.","usageExample":{"tool":"documents.import","args":{"collectionId":"collection-id","data":{},"performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"comments.list","signature":"comments.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List comments.","usageExample":{"tool":"comments.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"comments.info","signature":"comments.info(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Get comment details.","usageExample":{"tool":"comments.info","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"comments.create","signature":"comments.create(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Create a comment.","usageExample":{"tool":"comments.create","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"comments.update","signature":"comments.update(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Update a comment.","usageExample":{"tool":"comments.update","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"comments.delete","signature":"comments.delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Delete a comment.","usageExample":{"tool":"comments.delete","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"events.list","signature":"events.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List workspace events.","usageExample":{"tool":"events.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"oauth_clients.list","signature":"oauth_clients.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List OAuth clients.","usageExample":{"tool":"oauth_clients.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"oauth_clients.info","signature":"oauth_clients.info(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Get OAuth client details.","usageExample":{"tool":"oauth_clients.info","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"oauth_clients.create","signature":"oauth_clients.create(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Create an OAuth client.","usageExample":{"tool":"oauth_clients.create","args":{"name":"Example OAuth App","redirectUris":["https://example.com/callback"],"performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"oauth_clients.update","signature":"oauth_clients.update(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Update an OAuth client.","usageExample":{"tool":"oauth_clients.update","args":{"id":"oauth-client-id","name":"Updated OAuth App","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"oauth_clients.rotate_secret","signature":"oauth_clients.rotate_secret(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Rotate an OAuth client secret.","usageExample":{"tool":"oauth_clients.rotate_secret","args":{"id":"oauth-client-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"oauth_clients.delete","signature":"oauth_clients.delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Delete an OAuth client.","usageExample":{"tool":"oauth_clients.delete","args":{"id":"oauth-client-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"oauth_authentications.list","signature":"oauth_authentications.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List OAuth authentications.","usageExample":{"tool":"oauth_authentications.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"oauth_authentications.delete","signature":"oauth_authentications.delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Delete an OAuth authentication.","usageExample":{"tool":"oauth_authentications.delete","args":{"oauthClientId":"oauth-client-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"oauthClients.delete","signature":"oauthClients.delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Compatibility alias for oauth_clients.delete.","usageExample":{"tool":"oauthClients.delete","args":{"id":"oauth-client-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"oauthAuthentications.delete","signature":"oauthAuthentications.delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Compatibility alias for oauth_authentications.delete.","usageExample":{"tool":"oauthAuthentications.delete","args":{"oauthClientId":"oauth-client-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"data_attributes.list","signature":"data_attributes.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List data attributes.","usageExample":{"tool":"data_attributes.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"data_attributes.info","signature":"data_attributes.info(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Get data attribute details.","usageExample":{"tool":"data_attributes.info","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"data_attributes.create","signature":"data_attributes.create(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Create a data attribute.","usageExample":{"tool":"data_attributes.create","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"data_attributes.update","signature":"data_attributes.update(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Update a data attribute.","usageExample":{"tool":"data_attributes.update","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"data_attributes.delete","signature":"data_attributes.delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Delete a data attribute.","usageExample":{"tool":"data_attributes.delete","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"revisions.info","signature":"revisions.info(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Get revision details.","usageExample":{"tool":"revisions.info","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"documents.archived","signature":"documents.archived(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List archived documents.","usageExample":{"tool":"documents.archived","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"documents.deleted","signature":"documents.deleted(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List deleted documents.","usageExample":{"tool":"documents.deleted","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"documents.archive","signature":"documents.archive(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Archive a document.","usageExample":{"tool":"documents.archive","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"documents.restore","signature":"documents.restore(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Restore a document.","usageExample":{"tool":"documents.restore","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"documents.permanent_delete","signature":"documents.permanent_delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Permanently delete a document.","usageExample":{"tool":"documents.permanent_delete","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"documents.empty_trash","signature":"documents.empty_trash(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Empty document trash.","usageExample":{"tool":"documents.empty_trash","args":{"performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"webhooks.list","signature":"webhooks.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List webhooks.","usageExample":{"tool":"webhooks.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"webhooks.info","signature":"webhooks.info(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Get webhook details.","usageExample":{"tool":"webhooks.info","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"webhooks.create","signature":"webhooks.create(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Create a webhook.","usageExample":{"tool":"webhooks.create","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"webhooks.update","signature":"webhooks.update(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Update a webhook.","usageExample":{"tool":"webhooks.update","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"webhooks.delete","signature":"webhooks.delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Delete a webhook.","usageExample":{"tool":"webhooks.delete","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"users.list","signature":"users.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List users.","usageExample":{"tool":"users.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"users.info","signature":"users.info(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Get user details.","usageExample":{"tool":"users.info","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"users.invite","signature":"users.invite(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Invite a user.","usageExample":{"tool":"users.invite","args":{"email":"new.user@example.com","role":"member","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"users.update_role","signature":"users.update_role(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Update a user's workspace role.","usageExample":{"tool":"users.update_role","args":{"id":"user-id","role":"member","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"users.activate","signature":"users.activate(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Activate a user.","usageExample":{"tool":"users.activate","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"users.suspend","signature":"users.suspend(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Suspend a user.","usageExample":{"tool":"users.suspend","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"groups.list","signature":"groups.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List groups.","usageExample":{"tool":"groups.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"groups.info","signature":"groups.info(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Get group details.","usageExample":{"tool":"groups.info","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"groups.memberships","signature":"groups.memberships(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List group user memberships.","usageExample":{"tool":"groups.memberships","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"groups.create","signature":"groups.create(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Create a group.","usageExample":{"tool":"groups.create","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"groups.update","signature":"groups.update(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Update a group.","usageExample":{"tool":"groups.update","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"groups.delete","signature":"groups.delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Delete a group.","usageExample":{"tool":"groups.delete","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"groups.add_user","signature":"groups.add_user(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Add a user to a group.","usageExample":{"tool":"groups.add_user","args":{"id":"resource-id","userId":"user-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"groups.remove_user","signature":"groups.remove_user(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Remove a user from a group.","usageExample":{"tool":"groups.remove_user","args":{"id":"resource-id","userId":"user-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"collections.memberships","signature":"collections.memberships(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List collection user memberships.","usageExample":{"tool":"collections.memberships","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"collections.group_memberships","signature":"collections.group_memberships(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List collection group memberships.","usageExample":{"tool":"collections.group_memberships","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"collections.add_user","signature":"collections.add_user(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Add a user to a collection.","usageExample":{"tool":"collections.add_user","args":{"id":"resource-id","userId":"user-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"collections.remove_user","signature":"collections.remove_user(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Remove a user from a collection.","usageExample":{"tool":"collections.remove_user","args":{"id":"resource-id","userId":"user-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"collections.add_group","signature":"collections.add_group(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Add a group to a collection.","usageExample":{"tool":"collections.add_group","args":{"id":"resource-id","groupId":"group-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"collections.remove_group","signature":"collections.remove_group(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Remove a group from a collection.","usageExample":{"tool":"collections.remove_group","args":{"id":"resource-id","groupId":"group-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"documents.users","signature":"documents.users(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List users with access to a document.","usageExample":{"tool":"documents.users","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"documents.memberships","signature":"documents.memberships(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List document user memberships.","usageExample":{"tool":"documents.memberships","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"documents.group_memberships","signature":"documents.group_memberships(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List document group memberships.","usageExample":{"tool":"documents.group_memberships","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"documents.add_user","signature":"documents.add_user(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Add a user to a document.","usageExample":{"tool":"documents.add_user","args":{"id":"resource-id","userId":"user-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"documents.remove_user","signature":"documents.remove_user(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Remove a user from a document.","usageExample":{"tool":"documents.remove_user","args":{"id":"resource-id","userId":"user-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"documents.add_group","signature":"documents.add_group(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Add a group to a document.","usageExample":{"tool":"documents.add_group","args":{"id":"resource-id","groupId":"group-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"documents.remove_group","signature":"documents.remove_group(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Remove a group from a document.","usageExample":{"tool":"documents.remove_group","args":{"id":"resource-id","groupId":"group-id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"file_operations.list","signature":"file_operations.list(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"List file operations.","usageExample":{"tool":"file_operations.list","args":{}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"file_operations.info","signature":"file_operations.info(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Get file operation details.","usageExample":{"tool":"file_operations.info","args":{"id":"id"}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","Use includePolicies=true only when policy details are required."]},{"name":"file_operations.delete","signature":"file_operations.delete(args?: { ...endpointArgs; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Delete a file operation.","usageExample":{"tool":"file_operations.delete","args":{"id":"id","performAction":true}},"bestPractices":["Prefer minimal payloads to keep responses deterministic and token-efficient.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"documents.answer","signature":"documents.answer(args: { question?: string; query?: string; limit?: number; ...endpointArgs; includePolicies?: boolean; maxAttempts?: number })","description":"Answer a question using Outline AI over the selected document scope.","usageExample":{"tool":"documents.answer","args":{"question":"What changed in our onboarding checklist?","collectionId":"collection-id"}},"bestPractices":["Use question text that is specific enough to resolve citations quickly.","Scope by collectionId or documentId to reduce latency and hallucination risk.","If the deployment lacks documents.answerQuestion, this wrapper returns fallback retrieval evidence and a concrete search suggestion instead of a raw 404."]},{"name":"documents.answer_batch","signature":"documents.answer_batch(args: { question?: string; questions?: Array<string | { question?: string; query?: string; ...endpointArgs }>; limit?: number; ...endpointArgs; concurrency?: number; includePolicies?: boolean; maxAttempts?: number })","description":"Run multiple documents.answerQuestion calls with per-item isolation.","usageExample":{"tool":"documents.answer_batch","args":{"questions":["Where is the release checklist?","Who owns incident postmortems?"],"collectionId":"collection-id","concurrency":2}},"bestPractices":["Prefer small batches and low concurrency for predictable token and latency budgets.","Use per-item statuses to retry only failures.","Unsupported answer endpoints degrade to per-item retrieval evidence rather than failing the whole batch."]},{"name":"documents.backlinks","signature":"documents.backlinks(args: { id: string; limit?: number; offset?: number; sort?: string; direction?: 'ASC'|'DESC'; view?: 'ids'|'summary'|'full'; includePolicies?: boolean; maxAttempts?: number })","description":"List backlinks for a document via documents.list(backlinkDocumentId=id).","usageExample":{"tool":"documents.backlinks","args":{"id":"doc-1","limit":20,"view":"summary"}},"bestPractices":["Use view=ids for low-token planning loops, then hydrate specific documents separately.","Use limit/offset pagination for deterministic traversal over large backlink sets."]},{"name":"documents.graph_neighbors","signature":"documents.graph_neighbors(args: { id?: string; ids?: string[]; includeBacklinks?: boolean; includeSearchNeighbors?: boolean; searchQueries?: string[]; limitPerSource?: number; view?: 'ids'|'summary'|'full'; maxAttempts?: number })","description":"Collect one-hop graph neighbors and deterministic edge rows for source document IDs.","usageExample":{"tool":"documents.graph_neighbors","args":{"id":"doc-1","includeBacklinks":true,"includeSearchNeighbors":true,"searchQueries":["incident response"],"limitPerSource":8}},"bestPractices":["Start with a single source id and small limitPerSource, then expand incrementally.","Enable includeSearchNeighbors only when additional semantic neighborhood expansion is needed."]},{"name":"documents.graph_report","signature":"documents.graph_report(args: { seedIds: string[]; depth?: number; maxNodes?: number; includeBacklinks?: boolean; includeSearchNeighbors?: boolean; limitPerSource?: number; view?: 'ids'|'summary'|'full'; maxAttempts?: number })","description":"Build a bounded BFS graph report with stable nodes[] and edges[] output.","usageExample":{"tool":"documents.graph_report","args":{"seedIds":["doc-1","doc-2"],"depth":2,"maxNodes":120,"includeBacklinks":true,"includeSearchNeighbors":false,"limitPerSource":10}},"bestPractices":["Cap maxNodes and depth to keep traversal deterministic and cost-bounded.","Prefer view=ids for graph planning and fetch full nodes only for selected IDs."]},{"name":"documents.issue_refs","signature":"documents.issue_refs(args: { id?: string; ids?: string[]; issueDomains?: string[]; keyPattern?: string; view?: 'ids'|'summary'|'full'; maxAttempts?: number })","description":"Extract deterministic issue references (URL links and key-pattern matches) from one or more documents.","usageExample":{"tool":"documents.issue_refs","args":{"ids":["doc-1","doc-2"],"issueDomains":["jira.example.com","github.com"],"keyPattern":"[A-Z][A-Z0-9]+-\\\\d+","view":"summary"}},"bestPractices":["Start with view=ids for low-token audits, then re-run selected docs with summary/full views.","Provide issueDomains to reduce non-issue URL noise and keep outputs focused.","Tune keyPattern when your tracker uses custom issue key formats."]},{"name":"documents.issue_ref_report","signature":"documents.issue_ref_report(args: { query?: string; queries?: string[]; collectionId?: string; issueDomains?: string[]; keyPattern?: string; limit?: number; view?: 'ids'|'summary'|'full'; maxAttempts?: number })","description":"Resolve candidate documents from title+semantic search, then extract deterministic issue reference summaries.","usageExample":{"tool":"documents.issue_ref_report","args":{"queries":["incident response","release checklist"],"collectionId":"collection-id","issueDomains":["jira.example.com"],"limit":12,"view":"summary"}},"bestPractices":["Use specific queries to keep the candidate set small and deterministic.","Scope by collectionId when possible to avoid cross-workspace noise.","Review perQuery errors before treating missing issue refs as definitive."]},{"name":"documents.import_file","signature":"documents.import_file(args: { filePath: string; collectionId?: string; parentDocumentId?: string; publish?: boolean; contentType?: string; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean; ...endpointArgs })","description":"Upload a local file as multipart/form-data to documents.import while preserving deterministic output envelopes.","usageExample":{"tool":"documents.import_file","args":{"filePath":"./tmp/wiki-export.md","collectionId":"collection-id","publish":false,"performAction":true}},"bestPractices":["Provide exactly one placement target when needed: collectionId or parentDocumentId.","Use file_operations.info to poll async import status after documents.import_file returns.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"templates.extract_placeholders","signature":"templates.extract_placeholders(args: { id: string; maxAttempts?: number })","description":"Extract sorted unique placeholder keys ({{key}}) from template text nodes.","usageExample":{"tool":"templates.extract_placeholders","args":{"id":"template-id"}},"bestPractices":["Run this before document creation to validate required placeholder keys.","Use counts to catch repeated placeholders for deterministic pipeline checks."]},{"name":"documents.create_from_template","signature":"documents.create_from_template(args: { templateId: string; title?: string; collectionId?: string; parentDocumentId?: string; publish?: boolean; placeholderValues?: Record<string,string>; strictPlaceholders?: boolean; view?: 'summary'|'full'; includePolicies?: boolean; maxAttempts?: number; performAction?: boolean })","description":"Create from template, optionally inject placeholder values, and enforce strict unresolved-placeholder safety.","usageExample":{"tool":"documents.create_from_template","args":{"templateId":"template-id","title":"Service A - Incident Postmortem","placeholderValues":{"service_name":"Service A","owner":"SRE Team"},"strictPlaceholders":true,"publish":true,"performAction":true}},"bestPractices":["Keep strictPlaceholders=true in automation to prevent publishing unresolved template tokens.","Provide placeholderValues as exact key-value strings and inspect unresolvedCount on every run.","This tool is action-gated; set performAction=true only for explicitly confirmed mutations."]},{"name":"comments.review_queue","signature":"comments.review_queue(args: { documentIds?: string[]; collectionId?: string; includeAnchorText?: boolean; includeReplies?: boolean; limitPerDocument?: number; view?: 'summary'|'full'; maxAttempts?: number })","description":"Build a deterministic comment review queue from comments.list responses.","usageExample":{"tool":"comments.review_queue","args":{"documentIds":["doc-1","doc-2"],"includeReplies":true,"limitPerDocument":20}},"bestPractices":["Scope to explicit documentIds whenever possible for predictable queue size.","Use includeReplies=true to capture full threaded review context.","Treat truncated=true as a signal to re-run with a higher limitPerDocument."]},{"name":"federated.sync_manifest","signature":"federated.sync_manifest(args?: { collectionId?: string; query?: string; since?: string; limit?: number; offset?: number; includeDrafts?: boolean; maxAttempts?: number })","description":"Generate deterministic document manifest rows for federated index sync workflows.","usageExample":{"tool":"federated.sync_manifest","args":{"collectionId":"collection-id","since":"2026-03-01T00:00:00.000Z","limit":100,"offset":0}},"bestPractices":["Use `since` + pagination for incremental sync jobs.","Use includeDrafts=false for published-only downstream indexes.","Persist pagination.nextOffset and resume deterministically."]},{"name":"federated.sync_probe","signature":"federated.sync_probe(args: { query?: string; queries?: string[]; mode?: 'titles'|'semantic'|'both'; collectionId?: string; limit?: number; offset?: number; maxAttempts?: number })","description":"Probe document findability across title and semantic search with per-query ranked hits.","usageExample":{"tool":"federated.sync_probe","args":{"queries":["runbook escalation","incident policy"],"mode":"both","limit":8}},"bestPractices":["Use both mode when validating search behavior before external index reconciliation.","Inspect perQuery[].errors for partial-mode failures before alerting.","Track missing[] over time for regression detection."]},{"name":"federated.permission_snapshot","signature":"federated.permission_snapshot(args: { id?: string; ids?: string[]; query?: string; queries?: string[]; collectionId?: string; includeDocumentMemberships?: boolean; includeCollectionMemberships?: boolean; limitPerQuery?: number; membershipLimit?: number; concurrency?: number; maxAttempts?: number })","description":"Capture per-document permission and membership snapshots for federated ACL reconciliation.","usageExample":{"tool":"federated.permission_snapshot","args":{"ids":["doc-1","doc-2"],"includeDocumentMemberships":true,"includeCollectionMemberships":true}},"bestPractices":["Pass explicit ids for deterministic ACL snapshots.","Use query/queries only when you need dynamic resolution before snapshotting.","Inspect item.errors for scoped permission gaps instead of failing whole runs."]},{"name":"capabilities.map","signature":"capabilities.map(args?: { includePolicies?: boolean; includeRaw?: boolean })","description":"Return effective profile capabilities from auth context and optional policy summary.","usageExample":{"tool":"capabilities.map","args":{"includePolicies":true}},"bestPractices":["Call once before planning mutating operations.","Use includePolicies=true when you need per-resource ability inference.","Keep includeRaw=false unless debugging capability mismatches."]},{"name":"documents.cleanup_test","signature":"documents.cleanup_test(args?: { markerPrefix?: string; olderThanHours?: number; dryRun?: boolean; deleteMode?: 'safe'|'direct'; maxPages?: number; pageLimit?: number; concurrency?: number; allowUnsafePrefix?: boolean; performAction?: boolean })","description":"Find and optionally delete test-created documents by marker prefix.","usageExample":{"tool":"documents.cleanup_test","args":{"markerPrefix":"outline-cli-live-test-","olderThanHours":24,"dryRun":true,"deleteMode":"safe"}},"bestPractices":["Use dryRun=true first to review deletion set.","Keep markerPrefix specific to your test suite to avoid accidental deletes.","Use deleteMode=safe (default) to enforce read-token and revision checks before delete.","Avoid allowUnsafePrefix unless operating in an isolated sandbox.","When dryRun=false this tool is action-gated; set performAction=true only after explicit confirmation."]}]}
7
-
8
- ```
@@ -1,123 +0,0 @@
1
- import crypto from "node:crypto";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
- const repoRoot = path.resolve(__dirname, "..");
8
-
9
- const bindingFile = path.join(repoRoot, "src", "entry-integrity-binding.generated.js");
10
- const manifestFile = path.join(repoRoot, "src", "entry-integrity-manifest.generated.js");
11
-
12
- const excludedFiles = new Set([
13
- "src/entry-integrity-binding.generated.js",
14
- "src/entry-integrity-manifest.generated.js",
15
- ]);
16
-
17
- function digestHex(value) {
18
- return crypto.createHash("sha256").update(value).digest("hex");
19
- }
20
-
21
- function canonicalManifest(files) {
22
- return files
23
- .map((item) => ({
24
- path: item.path.replace(/\\/g, "/"),
25
- sha256: item.sha256,
26
- }))
27
- .sort((a, b) => a.path.localeCompare(b.path))
28
- .map((item) => `${item.path}:${item.sha256}`)
29
- .join("\n");
30
- }
31
-
32
- async function walk(dirPath) {
33
- const output = [];
34
- const entries = await fs.readdir(dirPath, { withFileTypes: true });
35
- entries.sort((a, b) => a.name.localeCompare(b.name));
36
- for (const entry of entries) {
37
- const abs = path.join(dirPath, entry.name);
38
- if (entry.isDirectory()) {
39
- output.push(...(await walk(abs)));
40
- continue;
41
- }
42
- const rel = path.relative(repoRoot, abs).replace(/\\/g, "/");
43
- if (!rel.endsWith(".js")) {
44
- continue;
45
- }
46
- if (excludedFiles.has(rel)) {
47
- continue;
48
- }
49
- output.push(rel);
50
- }
51
- return output;
52
- }
53
-
54
- async function hashFiles(files) {
55
- const output = [];
56
- for (const rel of files) {
57
- const abs = path.join(repoRoot, rel);
58
- const raw = await fs.readFile(abs);
59
- output.push({
60
- path: rel,
61
- sha256: digestHex(raw),
62
- });
63
- }
64
- return output;
65
- }
66
-
67
- function toBindingSource({ keySalt, keyId }) {
68
- return `// Auto-generated by scripts/generate-entry-integrity.mjs
69
- export const ENTRY_INTEGRITY_BINDING = Object.freeze({
70
- algorithm: "sha256-salted-manifest-v1",
71
- keyId: ${JSON.stringify(keyId)},
72
- keySalt: ${JSON.stringify(keySalt)},
73
- });
74
- `;
75
- }
76
-
77
- function toManifestSource({ generatedAt, signature, files }) {
78
- return `// Auto-generated by scripts/generate-entry-integrity.mjs
79
- export const ENTRY_INTEGRITY_MANIFEST = Object.freeze({
80
- version: 1,
81
- algorithm: "sha256",
82
- signatureAlgorithm: "sha256-salted-manifest-v1",
83
- signature: ${JSON.stringify(signature)},
84
- generatedAt: ${JSON.stringify(generatedAt)},
85
- files: ${JSON.stringify(files, null, 2)},
86
- });
87
- `;
88
- }
89
-
90
- async function main() {
91
- const buildKey = process.env.OUTLINE_ENTRY_BUILD_KEY || "dev-local-entry-integrity-key";
92
- const keySalt = digestHex(buildKey);
93
- const keyId = keySalt.slice(0, 12);
94
-
95
- const srcFiles = await walk(path.join(repoRoot, "src"));
96
- const hashes = await hashFiles(srcFiles);
97
- const signature = digestHex(`${keySalt}\n${canonicalManifest(hashes)}`);
98
-
99
- const generatedAt = new Date().toISOString();
100
- await fs.writeFile(bindingFile, toBindingSource({ keySalt, keyId }), "utf8");
101
- await fs.writeFile(
102
- manifestFile,
103
- toManifestSource({
104
- generatedAt,
105
- signature,
106
- files: hashes,
107
- }),
108
- "utf8"
109
- );
110
-
111
- process.stdout.write(
112
- `${JSON.stringify({
113
- ok: true,
114
- files: hashes.length,
115
- manifestFile: path.relative(repoRoot, manifestFile),
116
- bindingFile: path.relative(repoRoot, bindingFile),
117
- keyId,
118
- generatedAt,
119
- }, null, 2)}\n`
120
- );
121
- }
122
-
123
- await main();