@khanglvm/outline-cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.test.example +2 -0
- package/AGENTS.md +107 -0
- package/CHANGELOG.md +102 -0
- package/README.md +244 -0
- package/bin/outline-agent.js +5 -0
- package/bin/outline-cli.js +13 -0
- package/package.json +25 -0
- package/scripts/generate-entry-integrity.mjs +123 -0
- package/scripts/release.mjs +353 -0
- package/src/action-gate.js +257 -0
- package/src/agent-skills.js +759 -0
- package/src/cli.js +956 -0
- package/src/config-store.js +720 -0
- package/src/entry-integrity-binding.generated.js +6 -0
- package/src/entry-integrity-manifest.generated.js +74 -0
- package/src/entry-integrity.js +112 -0
- package/src/errors.js +15 -0
- package/src/outline-client.js +237 -0
- package/src/result-store.js +183 -0
- package/src/secure-keyring.js +290 -0
- package/src/tool-arg-schemas.js +2346 -0
- package/src/tools.extended.js +3252 -0
- package/src/tools.js +1056 -0
- package/src/tools.mutation.js +1807 -0
- package/src/tools.navigation.js +2273 -0
- package/src/tools.platform.js +554 -0
- package/src/utils.js +176 -0
- package/test/action-gate.unit.test.js +157 -0
- package/test/agent-skills.unit.test.js +52 -0
- package/test/config-store.unit.test.js +89 -0
- package/test/hardening.unit.test.js +3778 -0
- package/test/live.integration.test.js +5140 -0
- package/test/profile-selection.unit.test.js +279 -0
- package/test/security.unit.test.js +113 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
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.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1 - 2026-03-05
|
|
4
|
+
|
|
5
|
+
- Initial tagged release notes.
|
|
6
|
+
- feat(help): add ai skill guidance section for scenario workflows (81a6368)
|
|
7
|
+
- feat(uc19): add compliance oauth tests and docs (b97ab28)
|
|
8
|
+
- feat(uc19): add oauth client and auth wrappers (c1c31f8)
|
|
9
|
+
- feat(uc14): add permanent delete docs and tests (c336bb8)
|
|
10
|
+
- feat(uc14): add permanent delete wrapper (fa99d2e)
|
|
11
|
+
- feat(uc09): add apply_patch_safe tests and docs (41078ae)
|
|
12
|
+
- feat(uc09): add apply_patch_safe wrapper (bc87f7e)
|
|
13
|
+
- feat(uc07): add issue ref helper tests and docs (3254bf5)
|
|
14
|
+
- feat(uc07): add issue reference helper tools (ccf09bc)
|
|
15
|
+
- feat(uc13): add workspace automation tests and docs (75776b4)
|
|
16
|
+
- feat(uc13): add user lifecycle automation wrappers (877adbe)
|
|
17
|
+
- feat(uc12): add migration scenario tests and docs (db33d85)
|
|
18
|
+
- feat(uc12): add import and file operation primitives (25316c5)
|
|
19
|
+
- feat(uc11): add template pipeline tests and docs (c3b126d)
|
|
20
|
+
- feat(uc11): add placeholder pipeline tools (e5fe193)
|
|
21
|
+
- feat(uc10): add graph scenario tests and docs (e2f497a)
|
|
22
|
+
- feat(uc10): add graph traversal helpers (33432d9)
|
|
23
|
+
- feat(uc09): add rollback-safety scenario tests and docs (7bc947c)
|
|
24
|
+
- feat(uc09): add revisions diff helper (d94511e)
|
|
25
|
+
- feat(uc07): add issue-link scenario tests and docs (508281f)
|
|
26
|
+
- feat(uc07): add data-attribute wrappers and schema alignment (cec94ce)
|
|
27
|
+
- feat(uc06): add role-visibility scenario tests and docs (9f2b2ae)
|
|
28
|
+
- feat(uc06): harden role and membership contracts (76d33d7)
|
|
29
|
+
- feat(uc05): add sharing scenario tests and docs (fbc35a2)
|
|
30
|
+
- feat(uc05): harden share lifecycle contracts (6f594f0)
|
|
31
|
+
- feat(uc04): add internal-faq scenario coverage and docs (7cbb697)
|
|
32
|
+
- feat(uc03): add meeting-notes scenario coverage and docs (4f4fb0f)
|
|
33
|
+
- chore: checkpoint local outline-cli changes (9e75cd9)
|
|
34
|
+
- fix(validation): validate expectedRevision in documents.apply_patch (e51fb8b)
|
|
35
|
+
- test(schema): add coverage for federated and terminology tools (b925c48)
|
|
36
|
+
- feat(workflows): add federated and terminology composite tools (5b3fa36)
|
|
37
|
+
- test(schema): cover new scenario wrappers and safety rules (35401fb)
|
|
38
|
+
- feat(tools): add scenario-critical wrappers and safety hardening (d4d9fac)
|
|
39
|
+
- docs(usecases): improve UC-20 issue traceability (111f4fd)
|
|
40
|
+
- docs(usecases): improve UC-19 issue traceability (7dc1278)
|
|
41
|
+
- docs(usecases): improve UC-18 issue traceability (8d594f4)
|
|
42
|
+
- docs(usecases): improve UC-17 issue traceability (2da81d5)
|
|
43
|
+
- docs(usecases): improve UC-16 issue traceability (df28cb2)
|
|
44
|
+
- docs(usecases): improve UC-15 issue traceability (d831dac)
|
|
45
|
+
- docs(usecases): improve UC-14 issue traceability (9947f95)
|
|
46
|
+
- docs(usecases): improve UC-13 issue traceability (ecd07c7)
|
|
47
|
+
- docs(usecases): improve UC-12 issue traceability (7d3dfdc)
|
|
48
|
+
- docs(usecases): improve UC-11 issue traceability (1155797)
|
|
49
|
+
- docs(usecases): improve UC-10 issue traceability (8770670)
|
|
50
|
+
- docs(usecases): improve UC-09 issue traceability (01733dd)
|
|
51
|
+
- docs(usecases): improve UC-08 issue traceability (11b7892)
|
|
52
|
+
- docs(usecases): improve UC-07 issue traceability (66fd433)
|
|
53
|
+
- docs(usecases): improve UC-06 issue traceability (f769da8)
|
|
54
|
+
- docs(usecases): improve UC-05 issue traceability (0dc78b5)
|
|
55
|
+
- docs(usecases): improve UC-04 issue traceability (d8afaf4)
|
|
56
|
+
- docs(usecases): improve UC-03 issue traceability (b8ac03e)
|
|
57
|
+
- docs(usecases): improve UC-02 issue traceability (6305567)
|
|
58
|
+
- docs(usecases): improve UC-01 issue traceability (e3b3459)
|
|
59
|
+
- docs(usecases): add index for UC scenario artifacts (32ce730)
|
|
60
|
+
- docs(usecases): add UC-20 secure lifecycle governance improvement (3a5abe0)
|
|
61
|
+
- docs(usecases): add UC-19 compliance access auditability improvement (39a6843)
|
|
62
|
+
- docs(usecases): add UC-18 terminology refactor improvement (64e4df1)
|
|
63
|
+
- docs(usecases): add UC-17 editorial review lifecycle improvement (692cefa)
|
|
64
|
+
- docs(usecases): add UC-16 federated search sync improvement (b3bad9a)
|
|
65
|
+
- docs(usecases): add UC-15 ai semantic qa improvement (6cd384e)
|
|
66
|
+
- docs(usecases): add UC-14 webhook workflow improvement (9cadbea)
|
|
67
|
+
- docs(usecases): add UC-13 api driven workspace automation improvement (c80a6a9)
|
|
68
|
+
- docs(usecases): add UC-12 legacy wiki migration improvement (6c00a56)
|
|
69
|
+
- docs(usecases): add UC-11 template driven doc pipeline improvement (39a492b)
|
|
70
|
+
- docs(usecases): add UC-10 crosslinked knowledge graph improvement (4a90748)
|
|
71
|
+
- docs(usecases): add UC-09 postmortem rca rollback safety improvement (d6a02dd)
|
|
72
|
+
- docs(usecases): add UC-08 runbooks incident playbooks improvement (48df335)
|
|
73
|
+
- docs(usecases): add UC-07 project docs issue linkage improvement (c7804bb)
|
|
74
|
+
- docs(usecases): add UC-06 role based department spaces improvement (f3f14fe)
|
|
75
|
+
- docs(usecases): add UC-05 public docs sharing improvement (5f56b43)
|
|
76
|
+
- docs(usecases): add UC-04 internal faq knowledge base improvement (1d3c1c9)
|
|
77
|
+
- docs(usecases): add UC-03 meeting notes decision logs improvement (9a115d3)
|
|
78
|
+
- docs(usecases): add UC-02 sop policy wiki improvement (f34462e)
|
|
79
|
+
- docs(usecases): add UC-01 handbook onboarding improvement (1d41ab0)
|
|
80
|
+
- wip (5dae3ea)
|
|
81
|
+
|
|
82
|
+
All notable changes to this project are documented in this file.
|
|
83
|
+
|
|
84
|
+
The format follows Keep a Changelog principles and this project uses Semantic Versioning.
|
|
85
|
+
|
|
86
|
+
## Unreleased
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- Improved top-level documentation with a user-focused quickstart and clearer operational guidance.
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
|
|
94
|
+
- Added a compact AI-agent instruction section in `README.md` for deterministic low-token workflows.
|
|
95
|
+
|
|
96
|
+
## 0.1.0 - 2026-03-05
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- Initial release baseline for `@khanglvm/outline-cli`.
|
|
101
|
+
- Agent-optimized Outline CLI with deterministic JSON/NDJSON outputs, profile management, and batch invocation.
|
|
102
|
+
- Mutation safety gates (`performAction`) and safe delete flow with read-token confirmation.
|
package/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# outline-cli
|
|
2
|
+
|
|
3
|
+
`outline-cli` (alias `outline-agent`) is a Node.js CLI for the Outline API, designed for both human operators and AI agents.
|
|
4
|
+
|
|
5
|
+
It prioritizes deterministic, machine-friendly output:
|
|
6
|
+
|
|
7
|
+
- Stable JSON envelopes
|
|
8
|
+
- Token-efficient `ids` and `summary` views
|
|
9
|
+
- Batch operations to reduce API round trips
|
|
10
|
+
- Safe mutation gates (`performAction: true`)
|
|
11
|
+
- Automatic large-result offload to temp files
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
Prerequisites:
|
|
16
|
+
|
|
17
|
+
- Node.js `>=18.17`
|
|
18
|
+
- An Outline workspace URL
|
|
19
|
+
- An Outline API key (recommended auth mode)
|
|
20
|
+
|
|
21
|
+
Run from npm without installing:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @khanglvm/outline-cli --help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or install locally in this repo:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install
|
|
31
|
+
node ./bin/outline-cli.js --help
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Set up a profile (API key mode):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx @khanglvm/outline-cli profile add prod \
|
|
38
|
+
--base-url https://app.getoutline.com \
|
|
39
|
+
--api-key "$OUTLINE_API_KEY" \
|
|
40
|
+
--set-default
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Verify auth:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx @khanglvm/outline-cli profile test prod --pretty
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Run your first search:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx @khanglvm/outline-cli invoke documents.search \
|
|
53
|
+
--args '{"query":"oncall runbook","mode":"semantic","limit":5,"view":"summary"}' \
|
|
54
|
+
--pretty
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Day-to-Day Usage
|
|
58
|
+
|
|
59
|
+
Discover tools and contracts:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx @khanglvm/outline-cli tools list
|
|
63
|
+
npx @khanglvm/outline-cli tools contract documents.search --pretty
|
|
64
|
+
npx @khanglvm/outline-cli tools contract all --result-mode inline
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Read document metadata by id:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx @khanglvm/outline-cli invoke documents.info \
|
|
71
|
+
--args '{"id":"<document-id>","view":"summary"}'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Create a document:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx @khanglvm/outline-cli invoke documents.create \
|
|
78
|
+
--args '{"title":"Release Notes","text":"# Release Notes","publish":false,"view":"summary"}'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Update a document (mutation requires `performAction: true`):
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx @khanglvm/outline-cli invoke documents.update \
|
|
85
|
+
--args '{"id":"<document-id>","text":"\n\nUpdated by automation.","editMode":"append","performAction":true,"view":"summary"}'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Batch multiple calls:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npx @khanglvm/outline-cli batch --ops '[
|
|
92
|
+
{"tool":"collections.list","args":{"limit":5,"view":"summary"}},
|
|
93
|
+
{"tool":"documents.search","args":{"query":"incident","limit":5,"view":"ids"}}
|
|
94
|
+
]'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Safe Delete Flow
|
|
98
|
+
|
|
99
|
+
Delete is guarded by read-token confirmation.
|
|
100
|
+
|
|
101
|
+
1. Arm-delete read:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx @khanglvm/outline-cli invoke documents.info \
|
|
105
|
+
--args '{"id":"<document-id>","armDelete":true,"view":"summary"}'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
2. Copy the returned `deleteReadReceipt.token`, then delete:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx @khanglvm/outline-cli invoke documents.delete \
|
|
112
|
+
--args '{"id":"<document-id>","readToken":"<token>","performAction":true}'
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Output Modes and Temp Files
|
|
116
|
+
|
|
117
|
+
Output format:
|
|
118
|
+
|
|
119
|
+
- `--output json` (default)
|
|
120
|
+
- `--output ndjson` for stream-friendly parsing
|
|
121
|
+
|
|
122
|
+
Result mode:
|
|
123
|
+
|
|
124
|
+
- `--result-mode auto` (default): inline until payload is too large
|
|
125
|
+
- `--result-mode inline`: always inline JSON
|
|
126
|
+
- `--result-mode file`: always write to temp file and return file pointer
|
|
127
|
+
|
|
128
|
+
Temp-file management:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx @khanglvm/outline-cli tmp list
|
|
132
|
+
npx @khanglvm/outline-cli tmp cat /absolute/path/from/result.json
|
|
133
|
+
npx @khanglvm/outline-cli tmp gc --older-than-hours 24
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Profile Management
|
|
137
|
+
|
|
138
|
+
Add password-mode profile:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx @khanglvm/outline-cli profile add internal \
|
|
142
|
+
--base-url https://outline.company.com \
|
|
143
|
+
--auth-type password \
|
|
144
|
+
--username agent@company.com \
|
|
145
|
+
--password "$OUTLINE_PASSWORD"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Select default profile:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npx @khanglvm/outline-cli profile use prod
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Improve AI profile routing metadata:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npx @khanglvm/outline-cli profile annotate prod \
|
|
158
|
+
--description "Production knowledge base" \
|
|
159
|
+
--append-keywords "prod,runbook,incident"
|
|
160
|
+
|
|
161
|
+
npx @khanglvm/outline-cli profile enrich prod \
|
|
162
|
+
--query "incident escalation process" \
|
|
163
|
+
--titles "Incident Playbook,Escalation Matrix"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## AI Agent Mini Instructions
|
|
167
|
+
|
|
168
|
+
Use this short operating pattern when an AI agent drives the CLI:
|
|
169
|
+
|
|
170
|
+
1. Start with `tools contract all --result-mode inline`.
|
|
171
|
+
2. Prefer `view:"ids"` or `view:"summary"` first; hydrate to `full` only when needed.
|
|
172
|
+
3. Bundle independent reads into one `batch` call.
|
|
173
|
+
4. For any mutation endpoint, explicitly set `performAction:true`.
|
|
174
|
+
5. For delete, always run `documents.info` with `armDelete:true` first and pass the returned read token.
|
|
175
|
+
6. If output is file-offloaded, read only the required fields via `tmp cat` + `jq`.
|
|
176
|
+
|
|
177
|
+
For structured AI playbooks and scenario guides:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npx @khanglvm/outline-cli tools help ai-skills --view summary
|
|
181
|
+
npx @khanglvm/outline-cli tools help ai-skills --scenario UC-12
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Testing (Live Environment)
|
|
185
|
+
|
|
186
|
+
Set test credentials in local env file:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
cp .env.test.example .env.test.local
|
|
190
|
+
# set OUTLINE_TEST_BASE_URL and OUTLINE_TEST_API_KEY
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Run checks:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm run check
|
|
197
|
+
npm test
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Test rule in this repository:
|
|
201
|
+
|
|
202
|
+
- Mutation tests create and clean up their own test documents.
|
|
203
|
+
- Read-only tests may use site-wide data.
|
|
204
|
+
|
|
205
|
+
## Release and Publish
|
|
206
|
+
|
|
207
|
+
Standard release flow:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
npm run release -- --bump patch
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
This flow performs:
|
|
214
|
+
|
|
215
|
+
- Version bump
|
|
216
|
+
- `CHANGELOG.md` update
|
|
217
|
+
- Integrity refresh (`npm run integrity:refresh`)
|
|
218
|
+
- Verification (`npm run check`, `npm test`)
|
|
219
|
+
- `npm publish --access public`
|
|
220
|
+
- Git commit, tag, and push to `origin`
|
|
221
|
+
|
|
222
|
+
Prepare without publishing/pushing:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm run release:prepare -- --bump patch
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Release prerequisites:
|
|
229
|
+
|
|
230
|
+
- Clean working tree (unless you intentionally pass `--allow-dirty`)
|
|
231
|
+
- `OUTLINE_ENTRY_BUILD_KEY` available in environment or `.env.local`
|
|
232
|
+
- npm auth ready (`npm login`)
|
|
233
|
+
|
|
234
|
+
## Security Notes
|
|
235
|
+
|
|
236
|
+
- Never commit real API keys.
|
|
237
|
+
- Keep local secrets in untracked files such as `.env.test.local`.
|
|
238
|
+
- Profile secrets are stored in OS keychain by default.
|
|
239
|
+
|
|
240
|
+
## Reference Docs
|
|
241
|
+
|
|
242
|
+
- Tool contracts: [`docs/TOOL_CONTRACTS.md`](docs/TOOL_CONTRACTS.md)
|
|
243
|
+
- Agent rules for this repo: [`AGENTS.md`](AGENTS.md)
|
|
244
|
+
- Release script: [`scripts/release.mjs`](scripts/release.mjs)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { assertEntryIntegrity } from "../src/entry-integrity.js";
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
await assertEntryIntegrity();
|
|
6
|
+
const { run } = await import("../src/cli.js");
|
|
7
|
+
await run(process.argv);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
main().catch((err) => {
|
|
11
|
+
process.stderr.write(`${err?.stack || err}\n`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@khanglvm/outline-cli",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Agent-optimized CLI for Outline API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"outline-cli": "bin/outline-cli.js",
|
|
8
|
+
"outline-agent": "bin/outline-cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node ./bin/outline-cli.js",
|
|
12
|
+
"check": "node ./bin/outline-cli.js --help",
|
|
13
|
+
"test": "node --test ./test/**/*.test.js",
|
|
14
|
+
"integrity:refresh": "node ./scripts/generate-entry-integrity.mjs",
|
|
15
|
+
"release": "node ./scripts/release.mjs",
|
|
16
|
+
"release:prepare": "node ./scripts/release.mjs --no-publish --no-push"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18.17"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@napi-rs/keyring": "^1.2.0",
|
|
23
|
+
"commander": "^13.1.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
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();
|