@kespace/ks-cli-openclaw 0.1.28

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/bin/ks ADDED
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ exec "$HOME/.agents/skills/ks/bin/ks" "$@"
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@kespace/ks-cli-openclaw",
3
+ "version": "0.1.28",
4
+ "description": "KnowledgeSpace CLI + SKILL installer for OpenClaw",
5
+ "ksAgent": "openclaw",
6
+ "bin": {
7
+ "ks": "./bin/ks"
8
+ },
9
+ "scripts": {
10
+ "postinstall": "node ./scripts/postinstall.mjs",
11
+ "preuninstall": "node ./scripts/preuninstall.mjs"
12
+ },
13
+ "publishConfig": {
14
+ "registry": "https://registry.npmjs.org/",
15
+ "access": "public"
16
+ },
17
+ "files": [
18
+ "bin/",
19
+ "binaries/",
20
+ "scripts/",
21
+ "skills/"
22
+ ],
23
+ "engines": {
24
+ "node": ">=16"
25
+ }
26
+ }
@@ -0,0 +1,53 @@
1
+ import { join } from 'path';
2
+
3
+ /** @typedef {'cc' | 'openclaw' | 'hermes' | 'kepaw'} AgentId */
4
+
5
+ /** @type {Record<AgentId, { packageName: string, label: string, globalSkillsDirs: (home: string) => string[], projectSkillsDir: ((projectDir: string) => string) | null }>} */
6
+ export const AGENTS = {
7
+ cc: {
8
+ packageName: '@ke/ks-cli',
9
+ label: 'Claude Code',
10
+ globalSkillsDirs: (home) => [join(home, '.claude', 'skills')],
11
+ projectSkillsDir: (projectDir) => join(projectDir, '.claude', 'skills'),
12
+ },
13
+ openclaw: {
14
+ packageName: '@ke/ks-cli-openclaw',
15
+ label: 'OpenClaw',
16
+ globalSkillsDirs: (home) => [join(home, '.openclaw', 'skills')],
17
+ projectSkillsDir: (projectDir) => join(projectDir, '.agents', 'skills'),
18
+ },
19
+ hermes: {
20
+ packageName: '@ke/ks-cli-hermes',
21
+ label: 'Hermes',
22
+ globalSkillsDirs: (home) => [join(home, '.hermes', 'skills')],
23
+ projectSkillsDir: null,
24
+ },
25
+ kepaw: {
26
+ packageName: '@ke/ks-cli-kepaw',
27
+ label: 'Kepaw',
28
+ globalSkillsDirs: (home) => [
29
+ join(home, '.kepaw', 'skills'),
30
+ join(home, '.kepaw', 'workspace', '.agents', 'skills'),
31
+ ],
32
+ projectSkillsDir: null,
33
+ },
34
+ };
35
+
36
+ /**
37
+ * @param {{ name?: string, ksAgent?: string }} pkgJson
38
+ * @returns {AgentId}
39
+ */
40
+ export function resolveAgentId(pkgJson) {
41
+ if (pkgJson.ksAgent && AGENTS[pkgJson.ksAgent]) {
42
+ return /** @type {AgentId} */ (pkgJson.ksAgent);
43
+ }
44
+ const name = (pkgJson.name || '').toLowerCase();
45
+ if (name === '@ke/ks-cli' || name === '@kespace/ks-cli' || name.includes('ke-cc')) return 'cc';
46
+ if (name.includes('openclaw')) return 'openclaw';
47
+ if (name.includes('hermes')) return 'hermes';
48
+ if (name.includes('kepaw')) return 'kepaw';
49
+ throw new Error(
50
+ `ks: unknown agent package "${pkgJson.name || '(unnamed)'}". ` +
51
+ 'Expected @ke/ks-cli or @kespace/ks-cli (and -openclaw / -hermes / -kepaw variants).',
52
+ );
53
+ }
@@ -0,0 +1,250 @@
1
+ import {
2
+ copyFileSync,
3
+ mkdirSync,
4
+ symlinkSync,
5
+ unlinkSync,
6
+ existsSync,
7
+ readFileSync,
8
+ writeFileSync,
9
+ chmodSync,
10
+ rmSync,
11
+ } from 'fs';
12
+ import { join, dirname } from 'path';
13
+ import { homedir } from 'os';
14
+ import { AGENTS, resolveAgentId } from './agent-config.mjs';
15
+
16
+ const SHARED_SKILL_DIR_NAME = 'ks';
17
+
18
+ export function readPackageMeta(packageDir) {
19
+ const pkgJson = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'));
20
+ const agentId = resolveAgentId(pkgJson);
21
+ const agent = AGENTS[agentId];
22
+ return {
23
+ agentId,
24
+ agent,
25
+ pkgName: pkgJson.name,
26
+ pkgVersion: pkgJson.version,
27
+ };
28
+ }
29
+
30
+ export function install(packageDir) {
31
+ const { agentId, agent, pkgName, pkgVersion } = readPackageMeta(packageDir);
32
+ const home = homedir();
33
+ const isGlobal = process.env.npm_config_global === 'true';
34
+ const projectDir = process.env.INIT_CWD || process.cwd();
35
+ const sharedSkillDir = join(home, '.agents', 'skills', SHARED_SKILL_DIR_NAME);
36
+
37
+ try { chmodSync(join(packageDir, 'bin', 'ks'), 0o755); } catch {}
38
+
39
+ ensureSharedSkill(packageDir, sharedSkillDir, pkgVersion);
40
+ registerRef(sharedSkillDir, agentId, pkgName, pkgVersion);
41
+ updateSkillLock(sharedSkillDir, pkgName, pkgVersion);
42
+
43
+ for (const skillsDir of agent.globalSkillsDirs(home)) {
44
+ linkSkillDir(sharedSkillDir, skillsDir);
45
+ }
46
+ console.log(`ks: ${agent.label} skill ready`);
47
+
48
+ if (!isGlobal && agent.projectSkillsDir) {
49
+ linkSkillDir(sharedSkillDir, agent.projectSkillsDir(projectDir));
50
+ }
51
+
52
+ if (!isGlobal) {
53
+ linkGlobalBin(home, sharedSkillDir);
54
+ injectLocalBinPath(home);
55
+ }
56
+
57
+ console.log('ks: Run `ks login <your-appkey>` to configure authentication.');
58
+ console.log('ks: Get your key at https://space.ke.com/tokens');
59
+ }
60
+
61
+ export function uninstall(packageDir) {
62
+ const { agentId, agent } = readPackageMeta(packageDir);
63
+ const home = homedir();
64
+ const isGlobal = process.env.npm_config_global === 'true';
65
+ const projectDir = process.env.INIT_CWD || process.cwd();
66
+ const sharedSkillDir = join(home, '.agents', 'skills', SHARED_SKILL_DIR_NAME);
67
+
68
+ for (const skillsDir of agent.globalSkillsDirs(home)) {
69
+ removeSkillSymlink(skillsDir);
70
+ }
71
+
72
+ if (!isGlobal && agent.projectSkillsDir) {
73
+ removeSkillSymlink(agent.projectSkillsDir(projectDir));
74
+ }
75
+
76
+ const refs = unregisterRef(sharedSkillDir, agentId);
77
+ if (Object.keys(refs).length === 0) {
78
+ try {
79
+ rmSync(sharedSkillDir, { recursive: true, force: true });
80
+ console.log(`ks: removed ${sharedSkillDir}`);
81
+ } catch {}
82
+ removeSkillLockEntry(home);
83
+ } else {
84
+ console.log(`ks: shared skill kept for other agents (${Object.keys(refs).join(', ')})`);
85
+ }
86
+ }
87
+
88
+ function ensureSharedSkill(packageDir, sharedSkillDir, pkgVersion) {
89
+ mkdirSync(join(sharedSkillDir, 'bin'), { recursive: true });
90
+
91
+ const installedVersion = getInstalledVersion(sharedSkillDir);
92
+ if (installedVersion === pkgVersion) {
93
+ return;
94
+ }
95
+
96
+ copyFileSync(join(packageDir, 'skills', 'SKILL.md'), join(sharedSkillDir, 'SKILL.md'));
97
+
98
+ const binaryName = resolveBinaryName();
99
+ if (binaryName) {
100
+ copyFileSync(join(packageDir, 'binaries', binaryName), join(sharedSkillDir, 'bin', 'ks'));
101
+ chmodSync(join(sharedSkillDir, 'bin', 'ks'), 0o755);
102
+ }
103
+
104
+ writeInstalledVersion(sharedSkillDir, pkgVersion);
105
+ const action = installedVersion
106
+ ? `upgraded ${installedVersion} → ${pkgVersion}`
107
+ : `installed @${pkgVersion}`;
108
+ console.log(`ks: ${action} → ${sharedSkillDir}`);
109
+ }
110
+
111
+ function resolveBinaryName() {
112
+ const os = process.platform;
113
+ const arch = process.arch;
114
+ if (os === 'darwin' && arch === 'arm64') return 'ks-darwin-arm64';
115
+ if (os === 'darwin') return 'ks-darwin-amd64';
116
+ if (os === 'linux') return 'ks-linux-amd64';
117
+ console.warn(`ks: unsupported platform ${os}/${arch}, skipping binary install`);
118
+ return null;
119
+ }
120
+
121
+ function linkSkillDir(sourceDir, skillsParentDir) {
122
+ mkdirSync(skillsParentDir, { recursive: true });
123
+ const symlinkPath = join(skillsParentDir, SHARED_SKILL_DIR_NAME);
124
+ try { unlinkSync(symlinkPath); } catch {}
125
+ symlinkSync(sourceDir, symlinkPath);
126
+ console.log(`ks: symlinked → ${symlinkPath}`);
127
+ }
128
+
129
+ function removeSkillSymlink(skillsParentDir) {
130
+ const symlinkPath = join(skillsParentDir, SHARED_SKILL_DIR_NAME);
131
+ try {
132
+ unlinkSync(symlinkPath);
133
+ console.log(`ks: removed ${symlinkPath}`);
134
+ } catch {}
135
+ }
136
+
137
+ function refsPath(sharedSkillDir) {
138
+ return join(sharedSkillDir, '.refs.json');
139
+ }
140
+
141
+ function readRefs(sharedSkillDir) {
142
+ const path = refsPath(sharedSkillDir);
143
+ if (!existsSync(path)) return {};
144
+ try {
145
+ return JSON.parse(readFileSync(path, 'utf8'));
146
+ } catch {
147
+ return {};
148
+ }
149
+ }
150
+
151
+ function writeRefs(sharedSkillDir, refs) {
152
+ try {
153
+ writeFileSync(refsPath(sharedSkillDir), JSON.stringify(refs, null, 2));
154
+ } catch {}
155
+ }
156
+
157
+ function registerRef(sharedSkillDir, agentId, pkgName, pkgVersion) {
158
+ const refs = readRefs(sharedSkillDir);
159
+ refs[agentId] = { package: pkgName, version: pkgVersion };
160
+ writeRefs(sharedSkillDir, refs);
161
+ }
162
+
163
+ function unregisterRef(sharedSkillDir, agentId) {
164
+ const refs = readRefs(sharedSkillDir);
165
+ delete refs[agentId];
166
+ writeRefs(sharedSkillDir, refs);
167
+ return refs;
168
+ }
169
+
170
+ function linkGlobalBin(home, sharedSkillDir) {
171
+ const globalBin = join(sharedSkillDir, 'bin', 'ks');
172
+ const localBinDir = join(home, '.local', 'bin');
173
+ const localBinLink = join(localBinDir, 'ks');
174
+
175
+ if (!existsSync(globalBin)) {
176
+ console.warn('ks: global binary not found, skipping bin link');
177
+ return;
178
+ }
179
+
180
+ mkdirSync(localBinDir, { recursive: true });
181
+ try { unlinkSync(localBinLink); } catch {}
182
+ symlinkSync(globalBin, localBinLink);
183
+ console.log(`ks: linked → ${localBinLink}`);
184
+ }
185
+
186
+ function injectLocalBinPath(home) {
187
+ const profiles = ['.zshrc', '.bashrc', '.bash_profile', '.profile'].map((f) => join(home, f));
188
+ const line = 'export PATH="$HOME/.local/bin:$PATH"';
189
+ const marker = '# added by ks (knowledgespace)';
190
+ const written = [];
191
+
192
+ for (const p of profiles) {
193
+ if (!existsSync(p)) continue;
194
+ const content = readFileSync(p, 'utf8');
195
+ if (content.includes('.local/bin')) continue;
196
+ try {
197
+ writeFileSync(p, `${content}\n${marker}\n${line}\n`);
198
+ written.push(p);
199
+ } catch {}
200
+ }
201
+
202
+ if (written.length > 0) {
203
+ console.log(`ks: PATH updated for ~/.local/bin in ${written.join(', ')}`);
204
+ console.log('ks: 请执行 source ~/.zshrc 或重开终端使配置生效');
205
+ }
206
+ }
207
+
208
+ function updateSkillLock(skillPath, pkgName, pkgVersion) {
209
+ const lockPath = join(homedir(), '.agents', '.skill-lock.json');
210
+ let lock = { version: 3, skills: {} };
211
+ if (existsSync(lockPath)) {
212
+ try { lock = JSON.parse(readFileSync(lockPath, 'utf8')); } catch {}
213
+ }
214
+ if (!lock.skills) lock.skills = {};
215
+ const now = new Date().toISOString();
216
+ lock.skills[SHARED_SKILL_DIR_NAME] = {
217
+ source: SHARED_SKILL_DIR_NAME,
218
+ sourceType: 'npm',
219
+ sourceUrl: `${pkgName}@${pkgVersion}`,
220
+ skillPath,
221
+ version: pkgVersion,
222
+ installedAt: lock.skills[SHARED_SKILL_DIR_NAME]?.installedAt || now,
223
+ updatedAt: now,
224
+ };
225
+ try { writeFileSync(lockPath, JSON.stringify(lock, null, 2)); } catch {}
226
+ }
227
+
228
+ function removeSkillLockEntry(home) {
229
+ const lockPath = join(home, '.agents', '.skill-lock.json');
230
+ if (!existsSync(lockPath)) return;
231
+ try {
232
+ const lock = JSON.parse(readFileSync(lockPath, 'utf8'));
233
+ if (lock.skills) delete lock.skills[SHARED_SKILL_DIR_NAME];
234
+ writeFileSync(lockPath, JSON.stringify(lock, null, 2));
235
+ } catch {}
236
+ }
237
+
238
+ function getInstalledVersion(skillDir) {
239
+ try {
240
+ return readFileSync(join(skillDir, '.version'), 'utf8').trim();
241
+ } catch {
242
+ return null;
243
+ }
244
+ }
245
+
246
+ function writeInstalledVersion(skillDir, version) {
247
+ try {
248
+ writeFileSync(join(skillDir, '.version'), version);
249
+ } catch {}
250
+ }
@@ -0,0 +1,6 @@
1
+ import { dirname, resolve } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { install } from './install-lib.mjs';
4
+
5
+ const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
6
+ install(packageDir);
@@ -0,0 +1,6 @@
1
+ import { dirname, resolve } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { uninstall } from './install-lib.mjs';
4
+
5
+ const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
6
+ uninstall(packageDir);
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: ks
3
+ description: >
4
+ Bridge CLI to the team's shared knowledge store via KnowledgeSpace API.
5
+ Team files, documentation, and shared project knowledge live on remote
6
+ shared storage; ks lets agents read, write, search, and execute there
7
+ as if working locally.
8
+ Use when accessing SHARED team files or knowledge — NOT for local files
9
+ (use Read/Write/Bash for local operations instead).
10
+ Use `ks list` to discover available spaces, then pass --space-root per command.
11
+ Triggers: "shared files", "team knowledge base", "project docs on server",
12
+ "shared storage", "team notes", "find in knowledge base",
13
+ "check the shared codebase", "read from team storage",
14
+ "upload to knowledge base", "delete from shared", "remove from team docs",
15
+ "clean up shared files", "overwrite on server".
16
+ allowed-tools: Bash(ks:*)
17
+ ---
18
+
19
+ # ks — KnowledgeSpace CLI
20
+
21
+ Every command requires `--task-id` and `--space-root`:
22
+
23
+ ```bash
24
+ ks list # discover space names — no other flags needed
25
+ ```
26
+
27
+ **`--task-id`**: scopes staleness tracking to a task. Pick a short stable ID (8 chars, e.g. `$(uuidgen | cut -c1-8)`) and **reuse the same ID for all ks calls within the same task**. Different tasks may use different IDs. Changing the ID mid-task means the server loses your read history — staleness warnings will stop working.
28
+
29
+ ## Commands
30
+
31
+ | Command | Purpose |
32
+ |---------|---------|
33
+ | `ks list` | List available spaces |
34
+ | `ks read file <path>` | Read file — text/image/PDF/notebook auto-detected |
35
+ | `ks read raw <path>` | Read full file without pagination |
36
+ | `ks write file <path> <content>` | Write or append to file |
37
+ | `ks write patch <path>` | Edit file via string replace — safer than full write |
38
+ | `ks search <query>` | Search file contents or filenames |
39
+ | `ks exec cmd "<cmd>"` | Run shell command on server |
40
+ | `ks exec code "<code>" --lang python\|js\|bash` | Run code snippet on server |
41
+ | `ks upload <paths...>` | Upload files or folders to space |
42
+ | `ks delete <paths...>` | Delete files or folders from space |
43
+
44
+ Run `ks <command> --help` for flag details.
45
+
46
+ ## Space Context
47
+
48
+ Each space may contain an optional `SPACE.md` at its root. Read it first to understand what the space contains and how to search it effectively. **Never write or modify it** — it is maintained by the space owner.
49
+
50
+ ```bash
51
+ ks read file SPACE.md --task-id <id> --space-root <space>
52
+ ```
53
+
54
+ If the file does not exist (`FILE_NOT_FOUND`), proceed without it — `SPACE.md` is optional.
55
+
56
+ ### What SPACE.md typically contains
57
+
58
+ - **Purpose** — what domain or topic this space covers
59
+ - **Directory structure** — key subdirectories and what each holds
60
+ - **Search hints** — useful keywords, terminology, or query patterns for this space
61
+
62
+ ## Non-obvious Behaviors
63
+
64
+ ### mtime conflict detection
65
+
66
+ All write/patch commands accept `--mtime`. Pass the `mtime` from your last read response — the server returns `CONFLICT` instead of overwriting silently if the file changed in between. Always use it after reading.
67
+
68
+ ```bash
69
+ out=$(ks read file script.py --task-id <id> --space-root <space> --json)
70
+ mtime=$(echo "$out" | jq '.mtime')
71
+ ks write patch script.py --task-id <id> --space-root <space> \
72
+ --old "old text" --new "new text" --mtime "$mtime"
73
+ ```
74
+
75
+ ### Response fields to act on
76
+
77
+ | Field | When it appears | What to do |
78
+ |-------|----------------|------------|
79
+ | `hint` | Errors + truncated results | Follow it — gives exact recovery step or next `--offset` |
80
+ | `warning` | After write/patch | File changed since your last read; re-read before writing |
81
+ | `similar_files` | `FILE_NOT_FOUND` | Try these paths before retrying |
82
+ | `unchanged: true` | Read with `--mtime` | File unchanged; skip processing |
83
+ | `truncated: true` | Large result sets | Use `--offset` value from `hint` to page |
84
+ | `lint.status` | After writing `.py`/`.js` | Check `lint.output` if `error` |
85
+
86
+ ### REPEATED_SEARCH_BLOCKED
87
+
88
+ Running the same search 4 times in a row is blocked. You already have the information — stop re-searching and proceed with your task.
89
+
90
+ ### `ks write patch` errors
91
+
92
+ - `OLD_STRING_NOT_FOUND` — `--old` text not present; re-read the file before retrying
93
+ - `CONFLICT` — file changed since your `--mtime`; re-read and patch again
94
+
95
+ ## Temp Directory
96
+
97
+ `.ks_tmp/` inside the space root is for scripts that must exist on disk before `ks exec cmd`. Excluded from search. Clean up after use.
98
+
99
+ ## Destructive Operations (Upload & Delete)
100
+
101
+ Both `ks upload` and `ks delete` are destructive. They share the same confirmation pattern.
102
+
103
+ ### Agent Confirmation Rule (MANDATORY)
104
+
105
+ Before calling any destructive command, you MUST follow this exact sequence — no shortcuts:
106
+
107
+ 1. **Preview first**: Run with `--dry-run`
108
+ 2. **Display results**: Show the user what will happen (files, sizes, types, targets)
109
+ 3. **Wait for explicit confirmation**: Ask yes/no. **Do NOT proceed even if the user previously expressed intent.** Prior statements like "delete these" or "upload them" are NOT confirmation.
110
+ 4. **Execute only after confirmation**: Run with `--yes`
111
+
112
+ **Red flags — you are about to skip confirmation:**
113
+ - Thinking "the user clearly wants this"
114
+ - Treating an ambiguous reply as yes
115
+ - Skipping dry-run because the operation "looks safe"
116
+ - Proceeding because the user is repeating a prior request
117
+
118
+ **What counts as confirmation:** Only an unambiguous "yes" (or "no") reply to the dry-run preview shown in step 2. If the reply is ambiguous, re-ask: "Please confirm yes or no."
119
+
120
+ **What does NOT count as confirmation:**
121
+ - Any message before the dry-run output was shown
122
+ - Prior statements of intent ("delete those", "upload them")
123
+ - Ambiguous replies: "go ahead", "sure", "ok", "sounds good", "proceed", "do it"
124
+ - Conditional pre-authorizations ("if fewer than X files, just do it")
125
+
126
+ ---
127
+
128
+ ## Upload
129
+
130
+ ### Space Resolution Rule (MANDATORY)
131
+
132
+ If the user says vague phrases like "upload to knowledge base", "upload to team docs", "save to shared storage" without specifying a space:
133
+ 1. Call `ks list` to discover available spaces
134
+ 2. Present the space list to the user with descriptions
135
+ 3. Ask the user to select a specific space
136
+ 4. Proceed only after the user explicitly names a space
137
+
138
+ If the user does not specify a destination directory (`--dest-dir`):
139
+ 1. Ask the user which folder to upload to
140
+ 2. Suggest reasonable defaults based on file type or context
141
+ 3. Use `/` (root) only if the user explicitly agrees
142
+
143
+ ### Upload-specific confirmation
144
+
145
+ Apply the [Agent Confirmation Rule](#agent-confirmation-rule-mandatory) with:
146
+ - **Preview command**: `ks upload <paths> --space-root <space> --dest-dir <dir> --dry-run`
147
+ - **Display**: file list, target space, target directory, total size
148
+ - **Not confirmation**: user providing paths or destination without being asked after preview
149
+
150
+ ### Overwrite Rule (MANDATORY)
151
+
152
+ If the initial upload returns conflicts (files already exist):
153
+ 1. Display the conflicting files to the user
154
+ 2. Ask for overwrite confirmation
155
+ 3. Only if user confirms, re-upload with `--overwrite`
156
+
157
+ ### Examples
158
+
159
+ ```bash
160
+ # Preview (agent use)
161
+ ks upload ./src --space-root myspace --dest-dir docs/ --dry-run
162
+
163
+ # Upload after user confirms
164
+ ks upload ./src --space-root myspace --dest-dir docs/ --yes
165
+
166
+ # Overwrite after user confirms conflicts
167
+ ks upload ./src --space-root myspace --dest-dir docs/ --yes --overwrite
168
+ ```
169
+
170
+ ## Delete
171
+
172
+ ### Delete-specific confirmation
173
+
174
+ Apply the [Agent Confirmation Rule](#agent-confirmation-rule-mandatory) with:
175
+ - **Preview command**: `ks delete <paths> --space-root <space> --dry-run`
176
+ - **Display**: paths, types (file/folder), total items to be deleted
177
+ - **Not confirmation**: user listing paths to delete without being asked after preview
178
+
179
+ Deletion is destructive and cannot be undone. Never skip confirmation.
180
+
181
+ ### What can be deleted
182
+
183
+ `ks delete` removes files and folders that were created via the files API (`ks upload` or the web file manager). Files created by `ks write` (tools API) do not have database records and cannot be deleted with this command — use `ks exec cmd "rm ..."` for those.
184
+
185
+ ### Examples
186
+
187
+ ```bash
188
+ # Preview (agent use)
189
+ ks delete docs/old/ temp.txt --space-root myspace --dry-run
190
+
191
+ # Delete after user confirms
192
+ ks delete docs/old/ temp.txt --space-root myspace --yes
193
+ ```