@pokelo/cli 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -6
- package/package.json +2 -2
- package/skill/SKILL.md +265 -57
- package/src/cli.mjs +57 -8
- package/src/commands/docs.mjs +80 -2
- package/src/commands/projects.mjs +21 -2
- package/src/commands/search-workspace.mjs +22 -0
- package/src/commands/workspace.mjs +53 -0
- package/src/config.mjs +41 -3
- package/skill/reference.md +0 -59
- package/skill/scripts/ensure-pokelo.sh +0 -47
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ From this monorepo (before publish / local dev):
|
|
|
20
20
|
```bash
|
|
21
21
|
pnpm --filter @pokelo/cli exec pokelo --help
|
|
22
22
|
# or
|
|
23
|
-
node tools/pokelo-cli/bin/pokelo
|
|
23
|
+
node tools/pokelo-cli/bin/pokelo --help
|
|
24
24
|
|
|
25
25
|
cd tools/pokelo-cli && pnpm link --global
|
|
26
26
|
pokelo --help
|
|
@@ -70,9 +70,9 @@ pokelo auth status
|
|
|
70
70
|
### Config priority
|
|
71
71
|
|
|
72
72
|
```text
|
|
73
|
-
CLI flags (--url, --token, --project)
|
|
74
|
-
> environment (POKELO_URL, POKELO_TOKEN, POKELO_PROJECT)
|
|
75
|
-
> ~/.config/pokelo/config.json
|
|
73
|
+
CLI flags (--url, --token, --project, --workspace)
|
|
74
|
+
> environment (POKELO_URL, POKELO_TOKEN, POKELO_PROJECT, POKELO_WORKSPACE)
|
|
75
|
+
> ~/.config/pokelo/config.json (url, token, defaultProjectId, defaultWorkspaceId)
|
|
76
76
|
> legacy ~/.pokelo-cli.json (read-only migrate of url/token)
|
|
77
77
|
```
|
|
78
78
|
|
|
@@ -83,8 +83,13 @@ Create a Personal Access Token in the Pokelo UI (Settings → MCP / PAT). Prefix
|
|
|
83
83
|
## Commands
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
pokelo
|
|
86
|
+
pokelo workspace list
|
|
87
|
+
pokelo workspace show <id>
|
|
88
|
+
pokelo workspace use <id> # save as default (written to config.json)
|
|
89
|
+
|
|
90
|
+
pokelo projects list [--workspace <id>]
|
|
87
91
|
pokelo projects get <project-id-or-slug>
|
|
92
|
+
pokelo projects create --name "My Project" [--workspace <id>] [--description "..."]
|
|
88
93
|
|
|
89
94
|
pokelo search "how does auth work" --project <ref> --limit 5
|
|
90
95
|
|
|
@@ -93,6 +98,10 @@ pokelo docs get <document-id-or-slug> --project <ref>
|
|
|
93
98
|
pokelo docs create --title "Note" --type Note --content "# Hi" --project <ref>
|
|
94
99
|
pokelo docs update <id> --content-file ./body.md --project <ref>
|
|
95
100
|
|
|
101
|
+
pokelo docs versions <id> --project <ref> --limit 20
|
|
102
|
+
pokelo docs version <id> 7 --project <ref> --raw
|
|
103
|
+
pokelo docs restore <id> 7 --project <ref> --expect-head 9 --include-title
|
|
104
|
+
|
|
96
105
|
pokelo assets list --project <ref>
|
|
97
106
|
pokelo assets get <asset-id> --project <ref>
|
|
98
107
|
pokelo assets upload ./shot.png --project <ref> --description "screenshot"
|
|
@@ -103,7 +112,18 @@ pokelo assets delete <asset-id> --project <ref>
|
|
|
103
112
|
|
|
104
113
|
`assets upload` uses `POST …/assets/base64` for files ≤ 8 MiB; larger files use init → PUT → complete.
|
|
105
114
|
|
|
106
|
-
|
|
115
|
+
### Version history
|
|
116
|
+
|
|
117
|
+
`docs versions` lists snapshots newest first (any project member, viewers included). `docs version <n>`
|
|
118
|
+
returns one snapshot with its markdown body; `--raw` prints just the body. `docs restore <n>` makes an old
|
|
119
|
+
snapshot the new head — content always, title only with `--include-title` — and requires write access
|
|
120
|
+
(owner/admin/member). Pass `--expect-head <n>` (the `versionNo` you last saw) to get a `CONFLICT` error
|
|
121
|
+
instead of overwriting somebody else's newer edit.
|
|
122
|
+
|
|
123
|
+
The version subcommands also accept the explicit `<project> <document>` positional form, e.g.
|
|
124
|
+
`pokelo docs versions khirby article-ideas-d692f34a`.
|
|
125
|
+
|
|
126
|
+
Stdout is always JSON, except `docs version --raw`. Errors go to stderr.
|
|
107
127
|
|
|
108
128
|
Exit codes: `0` ok, `1` usage/config, `2` API/network.
|
|
109
129
|
|
|
@@ -111,6 +131,9 @@ Exit codes: `0` ok, `1` usage/config, `2` API/network.
|
|
|
111
131
|
|
|
112
132
|
```bash
|
|
113
133
|
pokelo auth login --url https://pokelo.example.com --token "$TOKEN"
|
|
134
|
+
pokelo workspace list # discover workspaces
|
|
135
|
+
pokelo workspace use <workspace-id> # set default (optional)
|
|
136
|
+
pokelo projects list # list projects (filtered by default workspace if set)
|
|
114
137
|
pokelo search "jak działa auth" --project khirby --limit 5
|
|
115
138
|
pokelo docs get <document-id> --project khirby
|
|
116
139
|
pokelo assets upload ./diagram.png --project khirby
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pokelo/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Official Pokelo CLI \u2014 public REST client for agents and developers",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Official Pokelo CLI \u2014 public REST client for agents and developers (multi-workspace)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"pokelo": "bin/pokelo"
|
package/skill/SKILL.md
CHANGED
|
@@ -1,98 +1,306 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pokelo-cli
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
agents without MCP,
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
Use the official Pokelo CLI (`pokelo` binary from npm package `@pokelo/cli`)
|
|
5
|
+
to access Pokelo through the public REST API using Personal Access Tokens.
|
|
6
|
+
Trigger when working with Pokelo knowledge bases from the shell, provisioning
|
|
7
|
+
agents without MCP, configuring self-hosted Pokelo instances, searching or
|
|
8
|
+
reading project knowledge, creating or updating Pokelo documents, managing
|
|
9
|
+
assets, or when the user mentions Pokelo CLI, `pokelo`, `@pokelo/cli`,
|
|
10
|
+
Pokelo PAT access, `POKELO_URL`, or `POKELO_TOKEN`.
|
|
10
11
|
---
|
|
11
12
|
|
|
12
13
|
# Pokelo CLI
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
Use the official `pokelo` shell client for Pokelo.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|---------|--------|
|
|
18
|
-
| npm package | `@pokelo/cli` |
|
|
19
|
-
| Command on PATH | `pokelo` |
|
|
20
|
-
| Config | flags > `POKELO_*` env > `~/.config/pokelo/config.json` |
|
|
17
|
+
The CLI talks to the **public REST `/v1` API using a Personal Access Token**.
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
**Never use MCP as the transport for this skill.**
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
## Core rules
|
|
22
|
+
|
|
23
|
+
* Use the existing `pokelo` binary when available.
|
|
24
|
+
* If `pokelo` is not available on `PATH`, install it with `npm install -g @pokelo/cli`.
|
|
25
|
+
* Never reinstall the CLI when `pokelo` is already available.
|
|
26
|
+
* Use the public REST API only.
|
|
27
|
+
* Never echo, print, log, or expose access tokens.
|
|
28
|
+
* Prefer environment variables in CI, sandboxes, and ephemeral agent environments.
|
|
29
|
+
* Support self-hosted Pokelo instances by treating the instance URL as part of auth configuration.
|
|
30
|
+
* Prefer semantic search before listing large document trees.
|
|
31
|
+
* Fetch the full document only when search snippets are insufficient.
|
|
32
|
+
* Perform write operations only when the user explicitly asks to create, update, upload, or delete something.
|
|
33
|
+
* After creating or updating a Pokelo document, return its direct URL when the CLI response provides one.
|
|
34
|
+
|
|
35
|
+
## 1. Ensure CLI availability
|
|
36
|
+
|
|
37
|
+
Before the first `pokelo` call in a session:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
command -v pokelo >/dev/null 2>&1 || npm install -g @pokelo/cli
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Then use:
|
|
25
44
|
|
|
26
45
|
```bash
|
|
27
|
-
|
|
28
|
-
bash skill/scripts/ensure-pokelo.sh
|
|
29
|
-
# Or: command -v pokelo || npm install -g @pokelo/cli
|
|
46
|
+
pokelo ...
|
|
30
47
|
```
|
|
31
48
|
|
|
32
|
-
|
|
49
|
+
Do not use helper install scripts.
|
|
33
50
|
|
|
34
|
-
|
|
35
|
-
2. Else: `npm install -g @pokelo/cli` (exposes the `pokelo` binary).
|
|
36
|
-
3. In this monorepo only, if npm package is unpublished: use
|
|
37
|
-
`node tools/pokelo-cli/bin/pokelo.mjs` or symlink it to `~/.local/bin/pokelo`.
|
|
51
|
+
Do not reinstall `@pokelo/cli` if `pokelo` already exists on `PATH`.
|
|
38
52
|
|
|
39
|
-
|
|
53
|
+
If installation fails, stop and report the installation error instead of falling back to MCP.
|
|
40
54
|
|
|
41
55
|
## 2. Auth
|
|
42
56
|
|
|
43
|
-
|
|
57
|
+
Pokelo may be self-hosted, so always treat the instance URL and token as configuration.
|
|
58
|
+
|
|
59
|
+
Configuration priority:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
explicit CLI flags
|
|
63
|
+
> POKELO_* environment variables
|
|
64
|
+
> ~/.config/pokelo/config.json
|
|
65
|
+
> defaults
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Ephemeral / CI / sandbox
|
|
69
|
+
|
|
70
|
+
Prefer environment variables:
|
|
44
71
|
|
|
45
72
|
```bash
|
|
46
|
-
export POKELO_URL=https
|
|
47
|
-
export POKELO_TOKEN
|
|
48
|
-
export POKELO_PROJECT=<slug-or-uuid>
|
|
73
|
+
export POKELO_URL=https://pokelo.example.com
|
|
74
|
+
export POKELO_TOKEN=...
|
|
75
|
+
export POKELO_PROJECT=<slug-or-uuid> # optional
|
|
49
76
|
```
|
|
50
77
|
|
|
51
|
-
|
|
78
|
+
### Persistent agent configuration
|
|
52
79
|
|
|
53
80
|
```bash
|
|
54
|
-
pokelo auth login
|
|
55
|
-
|
|
56
|
-
|
|
81
|
+
pokelo auth login \
|
|
82
|
+
--url "$POKELO_URL" \
|
|
83
|
+
--token-stdin <<< "$POKELO_TOKEN"
|
|
57
84
|
```
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
Direct token form:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pokelo auth login \
|
|
90
|
+
--url https://pokelo.example.com \
|
|
91
|
+
--token "$TOKEN"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use `--token-stdin` when practical because process arguments may be visible to other processes.
|
|
95
|
+
|
|
96
|
+
Verify:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pokelo auth status
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`auth status` must never reveal the full token.
|
|
103
|
+
|
|
104
|
+
## 3. Resolve the workspace (when needed)
|
|
105
|
+
|
|
106
|
+
Some commands accept an optional `--workspace <id>` filter. Resolution order:
|
|
107
|
+
|
|
108
|
+
1. `--workspace <id>` flag (or `-w <id>`)
|
|
109
|
+
2. `POKELO_WORKSPACE` environment variable
|
|
110
|
+
3. `defaultWorkspaceId` in `~/.config/pokelo/config.json`
|
|
111
|
+
4. Auto-detect: if exactly one workspace, use it; otherwise error
|
|
60
112
|
|
|
61
|
-
|
|
113
|
+
Set the default workspace for all future commands:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pokelo workspace list # list available workspaces
|
|
117
|
+
pokelo workspace use <id> # save as defaultWorkspaceId
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Show or verify a specific workspace:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pokelo workspace show <id>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 4. Resolve the project
|
|
127
|
+
|
|
128
|
+
When the project is unknown or ambiguous:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pokelo projects list [--workspace <id>]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Then:
|
|
62
135
|
|
|
63
136
|
```bash
|
|
64
|
-
pokelo projects list
|
|
65
137
|
pokelo projects get <project-ref>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Create a project (workspace admin required):
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pokelo projects create --name "My Project" [--workspace <id>] [--description "..."]
|
|
144
|
+
```
|
|
66
145
|
|
|
67
|
-
|
|
146
|
+
Prefer an already known project slug or `POKELO_PROJECT` when available.
|
|
68
147
|
|
|
69
|
-
|
|
70
|
-
pokelo docs get <doc-ref> --project <ref>
|
|
71
|
-
pokelo docs create --title "…" [--type] [--content|--content-file] [--parent] --project <ref>
|
|
72
|
-
pokelo docs update <doc-ref> [--title] [--content|--content-file] --project <ref>
|
|
148
|
+
Do not repeatedly list projects when the correct project is already known.
|
|
73
149
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
pokelo
|
|
150
|
+
## 4. Read knowledge
|
|
151
|
+
|
|
152
|
+
Use semantic search first:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
pokelo search "<query>" --project <project-ref> --limit 10
|
|
80
156
|
```
|
|
81
157
|
|
|
82
|
-
|
|
158
|
+
Typical workflow:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
search
|
|
162
|
+
-> inspect matches
|
|
163
|
+
-> docs get when more context is needed
|
|
164
|
+
-> answer
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Fetch the full document only when needed:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pokelo docs get <document-ref> --project <project-ref>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
List documents only when semantic search is not sufficient:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pokelo docs list --project <project-ref>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Avoid dumping large document trees into model context unless necessary.
|
|
180
|
+
|
|
181
|
+
## 5. Write knowledge
|
|
182
|
+
|
|
183
|
+
Only write when explicitly requested or when the user clearly asks to persist durable project knowledge.
|
|
184
|
+
|
|
185
|
+
### Create
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
pokelo docs create \
|
|
189
|
+
--title "Document title" \
|
|
190
|
+
--project <project-ref> \
|
|
191
|
+
--content-file ./document.md
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Optional fields may include:
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
--type
|
|
198
|
+
--parent
|
|
199
|
+
--content
|
|
200
|
+
--content-file
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Update
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
pokelo docs update <document-ref> \
|
|
207
|
+
--project <project-ref> \
|
|
208
|
+
--content-file ./document.md
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Prefer updating an existing relevant document instead of creating a duplicate.
|
|
212
|
+
|
|
213
|
+
After successful create or update, return the direct Pokelo document URL from the CLI response when available.
|
|
214
|
+
|
|
215
|
+
Do not store:
|
|
216
|
+
|
|
217
|
+
* credentials,
|
|
218
|
+
* API keys,
|
|
219
|
+
* passwords,
|
|
220
|
+
* access tokens,
|
|
221
|
+
* temporary debugging output,
|
|
222
|
+
* unconfirmed speculation.
|
|
223
|
+
|
|
224
|
+
### Version history
|
|
225
|
+
|
|
226
|
+
Every write to a document creates a snapshot. Use history to inspect what changed and to undo a bad edit
|
|
227
|
+
instead of retyping the old body.
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
pokelo docs versions <document-ref> --project <project-ref> [--page <n>] [--limit <n>]
|
|
231
|
+
pokelo docs version <document-ref> <version-no> --project <project-ref> [--raw]
|
|
232
|
+
pokelo docs restore <document-ref> <version-no> --project <project-ref> [--expect-head <n>] [--include-title]
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Notes:
|
|
236
|
+
|
|
237
|
+
* `versions` returns snapshots newest first with `versionNo`, `createdAt`, `createdBy`, `createdVia`
|
|
238
|
+
(`editor` / `api` / `mcp` / `assist` / `import` / `sync` / `conflict_resolve` / `restore` / `system`),
|
|
239
|
+
`contentSize` and `isCurrent` — not the body.
|
|
240
|
+
* `version` returns one snapshot including `contentMd`; `--raw` prints only the markdown body.
|
|
241
|
+
* `restore` creates a NEW head version from an old one — nothing is deleted. Content is always restored,
|
|
242
|
+
the title only with `--include-title`. Requires project write access (owner / admin / member).
|
|
243
|
+
* Read the current `versionNo` first and pass it as `--expect-head`; a `CONFLICT` error then means somebody
|
|
244
|
+
edited the document in the meantime — re-read the history instead of retrying blindly.
|
|
245
|
+
* Restore only when the user asked to undo or roll back.
|
|
246
|
+
|
|
247
|
+
## 6. Assets
|
|
248
|
+
|
|
249
|
+
Use asset commands when the user asks to work with files stored in Pokelo.
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
pokelo assets list --project <project-ref>
|
|
253
|
+
pokelo assets get <asset-id> --project <project-ref>
|
|
254
|
+
pokelo assets upload <file> --project <project-ref> [options]
|
|
255
|
+
pokelo assets download <asset-id> --out <path> --project <project-ref>
|
|
256
|
+
pokelo assets update <asset-id> --project <project-ref> [options]
|
|
257
|
+
pokelo assets delete <asset-id> --project <project-ref>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Do not expose storage implementation details unless debugging the CLI itself.
|
|
261
|
+
|
|
262
|
+
## 7. Output handling
|
|
263
|
+
|
|
264
|
+
Treat stdout as machine-readable JSON unless documented otherwise (`docs version --raw` prints markdown).
|
|
265
|
+
|
|
266
|
+
Rules:
|
|
267
|
+
|
|
268
|
+
* parse stdout,
|
|
269
|
+
* send errors to stderr,
|
|
270
|
+
* use exit status to distinguish success from failure,
|
|
271
|
+
* do not rely on decorative terminal output,
|
|
272
|
+
* never expose secrets in errors.
|
|
273
|
+
|
|
274
|
+
Expected exit codes:
|
|
275
|
+
|
|
276
|
+
```text
|
|
277
|
+
0 = success
|
|
278
|
+
1 = usage or configuration error
|
|
279
|
+
2 = API or network error
|
|
280
|
+
```
|
|
83
281
|
|
|
84
|
-
|
|
282
|
+
## 8. Recommended agent workflow
|
|
85
283
|
|
|
86
|
-
|
|
284
|
+
1. Check whether `pokelo` exists.
|
|
285
|
+
2. If not, install `@pokelo/cli`.
|
|
286
|
+
3. Resolve auth from flags, environment, or persisted config.
|
|
287
|
+
4. Resolve the project.
|
|
288
|
+
5. Run semantic search.
|
|
289
|
+
6. Fetch the full document if needed.
|
|
290
|
+
7. Answer from retrieved knowledge.
|
|
291
|
+
8. Write only when requested.
|
|
292
|
+
9. Return Pokelo document URLs after successful creates or updates.
|
|
87
293
|
|
|
88
|
-
|
|
89
|
-
2. Confirm auth (`auth status` or env vars).
|
|
90
|
-
3. Resolve project (`projects list` / known slug).
|
|
91
|
-
4. `search` → if snippets are thin, `docs get`.
|
|
92
|
-
5. Write only when asked (`docs create/update`, `assets upload`).
|
|
93
|
-
6. Do **not** echo tokens; do **not** call MCP from this skill.
|
|
294
|
+
## 9. Prohibited behavior
|
|
94
295
|
|
|
95
|
-
|
|
296
|
+
Do not:
|
|
96
297
|
|
|
97
|
-
|
|
98
|
-
|
|
298
|
+
* reinstall `@pokelo/cli` when `pokelo` already exists,
|
|
299
|
+
* use helper installer scripts,
|
|
300
|
+
* call the Pokelo MCP endpoint,
|
|
301
|
+
* construct MCP JSON-RPC requests,
|
|
302
|
+
* fall back from REST to MCP,
|
|
303
|
+
* print or expose PATs,
|
|
304
|
+
* list large document sets when semantic search is enough,
|
|
305
|
+
* create duplicate documentation when an existing document should be updated,
|
|
306
|
+
* perform writes without user intent.
|
package/src/cli.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import { runAuth } from './commands/auth.mjs';
|
|
|
4
4
|
import { runDocs } from './commands/docs.mjs';
|
|
5
5
|
import { runProjects } from './commands/projects.mjs';
|
|
6
6
|
import { runSearch } from './commands/search.mjs';
|
|
7
|
+
import { runSearchWorkspace } from './commands/search-workspace.mjs';
|
|
8
|
+
import { runWorkspace } from './commands/workspace.mjs';
|
|
7
9
|
|
|
8
10
|
function printHelp() {
|
|
9
11
|
process.stdout.write(
|
|
@@ -15,15 +17,24 @@ Usage:
|
|
|
15
17
|
pokelo auth login
|
|
16
18
|
pokelo auth status
|
|
17
19
|
|
|
18
|
-
pokelo
|
|
20
|
+
pokelo workspace list
|
|
21
|
+
pokelo workspace show <id>
|
|
22
|
+
pokelo workspace use <id>
|
|
23
|
+
|
|
24
|
+
pokelo projects list [--workspace <id>]
|
|
19
25
|
pokelo projects get <project-id-or-slug>
|
|
26
|
+
pokelo projects create --name <name> [--workspace <id>] [--description <text>]
|
|
20
27
|
|
|
21
28
|
pokelo search "<query>" [--project <ref>] [--limit <n>]
|
|
29
|
+
pokelo search-workspace "<query>" [--per-project-limit <n>]
|
|
22
30
|
|
|
23
31
|
pokelo docs list [--project <ref>]
|
|
24
32
|
pokelo docs get <document-id-or-slug> [--project <ref>]
|
|
25
33
|
pokelo docs create --title <t> [--type] [--content|--content-file] [--parent] [--project]
|
|
26
34
|
pokelo docs update <document-id-or-slug> [options]
|
|
35
|
+
pokelo docs versions [<project>] <document> [--page <n>] [--limit <n>]
|
|
36
|
+
pokelo docs version [<project>] <document> <version-no> [--raw]
|
|
37
|
+
pokelo docs restore [<project>] <document> <version-no> [--expect-head <n>] [--include-title]
|
|
27
38
|
|
|
28
39
|
pokelo assets list [--project] [--type] [--visibility] [--document]
|
|
29
40
|
pokelo assets get <asset-id> [--project]
|
|
@@ -33,14 +44,16 @@ Usage:
|
|
|
33
44
|
pokelo assets delete <asset-id> [--project]
|
|
34
45
|
|
|
35
46
|
Global options:
|
|
36
|
-
--project, -p <ref>
|
|
37
|
-
--
|
|
38
|
-
--
|
|
39
|
-
--
|
|
40
|
-
--
|
|
47
|
+
--project, -p <ref> Project UUID or slug (or POKELO_PROJECT / config default)
|
|
48
|
+
--workspace, -w <id> Workspace UUID (or POKELO_WORKSPACE / config defaultWorkspaceId)
|
|
49
|
+
--url <url> Instance URL (overrides env/config)
|
|
50
|
+
--token <token> Personal Access Token (overrides env/config; prefer env/--token-stdin)
|
|
51
|
+
--json Accepted for compatibility (stdout is always JSON, except docs version --raw)
|
|
52
|
+
--raw docs version: print the markdown body instead of JSON
|
|
53
|
+
--help, -h Show help
|
|
41
54
|
|
|
42
55
|
Config (priority: flags > env > file):
|
|
43
|
-
POKELO_URL, POKELO_TOKEN, POKELO_PROJECT
|
|
56
|
+
POKELO_URL, POKELO_TOKEN, POKELO_PROJECT, POKELO_WORKSPACE
|
|
44
57
|
~/.config/pokelo/config.json
|
|
45
58
|
|
|
46
59
|
Exit codes: 0 ok, 1 usage/config, 2 API/network error
|
|
@@ -79,6 +92,14 @@ export function parseArgs(argv) {
|
|
|
79
92
|
flags.tree = true;
|
|
80
93
|
continue;
|
|
81
94
|
}
|
|
95
|
+
if (arg === '--raw') {
|
|
96
|
+
flags.raw = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (arg === '--include-title') {
|
|
100
|
+
flags.includeTitle = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
82
103
|
|
|
83
104
|
const eq = arg.match(/^--([a-zA-Z0-9-]+)=(.*)$/);
|
|
84
105
|
if (eq) {
|
|
@@ -90,6 +111,10 @@ export function parseArgs(argv) {
|
|
|
90
111
|
flags.project = needValue(argv, ++i, arg);
|
|
91
112
|
continue;
|
|
92
113
|
}
|
|
114
|
+
if (arg === '--workspace' || arg === '-w') {
|
|
115
|
+
flags.workspace = needValue(argv, ++i, arg);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
93
118
|
if (arg === '--limit' || arg === '-l') {
|
|
94
119
|
flags.limit = parseInt(needValue(argv, ++i, arg), 10);
|
|
95
120
|
continue;
|
|
@@ -98,6 +123,17 @@ export function parseArgs(argv) {
|
|
|
98
123
|
flags.page = parseInt(needValue(argv, ++i, arg), 10);
|
|
99
124
|
continue;
|
|
100
125
|
}
|
|
126
|
+
if (arg === '--expect-head') {
|
|
127
|
+
flags.expectHead = parseInt(needValue(argv, ++i, arg), 10);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (arg === '--per-project-limit') {
|
|
131
|
+
const raw = needValue(argv, ++i, arg);
|
|
132
|
+
const n = Number(raw);
|
|
133
|
+
if (!Number.isInteger(n) || n < 1) fail(`--per-project-limit must be a positive integer, got: ${raw}`);
|
|
134
|
+
flags['per-project-limit'] = n;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
101
137
|
if (arg === '--url') {
|
|
102
138
|
flags.url = needValue(argv, ++i, arg);
|
|
103
139
|
continue;
|
|
@@ -180,8 +216,10 @@ function needValue(argv, index, flag) {
|
|
|
180
216
|
function setFlag(flags, name, value) {
|
|
181
217
|
const map = {
|
|
182
218
|
project: 'project',
|
|
219
|
+
workspace: 'workspace',
|
|
183
220
|
limit: 'limit',
|
|
184
221
|
page: 'page',
|
|
222
|
+
'expect-head': 'expectHead',
|
|
185
223
|
url: 'url',
|
|
186
224
|
token: 'token',
|
|
187
225
|
title: 'title',
|
|
@@ -190,6 +228,7 @@ function setFlag(flags, name, value) {
|
|
|
190
228
|
'content-file': 'contentFile',
|
|
191
229
|
parent: 'parent',
|
|
192
230
|
status: 'status',
|
|
231
|
+
'per-project-limit': 'per-project-limit',
|
|
193
232
|
search: 'search',
|
|
194
233
|
name: 'name',
|
|
195
234
|
mime: 'mime',
|
|
@@ -203,8 +242,12 @@ function setFlag(flags, name, value) {
|
|
|
203
242
|
};
|
|
204
243
|
const key = map[name];
|
|
205
244
|
if (!key) fail(`unknown option: --${name}`);
|
|
206
|
-
if (key === 'limit' || key === 'page') {
|
|
245
|
+
if (key === 'limit' || key === 'page' || key === 'expectHead') {
|
|
207
246
|
flags[key] = parseInt(value, 10);
|
|
247
|
+
} else if (key === 'per-project-limit') {
|
|
248
|
+
const n = Number(value);
|
|
249
|
+
if (!Number.isInteger(n) || n < 1) fail(`--per-project-limit must be a positive integer, got: ${value}`);
|
|
250
|
+
flags[key] = n;
|
|
208
251
|
} else {
|
|
209
252
|
flags[key] = value;
|
|
210
253
|
}
|
|
@@ -231,12 +274,18 @@ export async function main(argv) {
|
|
|
231
274
|
case 'auth':
|
|
232
275
|
await runAuth(rest, flags);
|
|
233
276
|
break;
|
|
277
|
+
case 'workspace':
|
|
278
|
+
await runWorkspace(rest, flags);
|
|
279
|
+
break;
|
|
234
280
|
case 'projects':
|
|
235
281
|
await runProjects(rest, flags);
|
|
236
282
|
break;
|
|
237
283
|
case 'search':
|
|
238
284
|
await runSearch(rest, flags);
|
|
239
285
|
break;
|
|
286
|
+
case 'search-workspace':
|
|
287
|
+
await runSearchWorkspace(rest, flags);
|
|
288
|
+
break;
|
|
240
289
|
case 'docs':
|
|
241
290
|
await runDocs(rest, flags);
|
|
242
291
|
break;
|
package/src/commands/docs.mjs
CHANGED
|
@@ -26,6 +26,33 @@ function resolveContent(flags) {
|
|
|
26
26
|
return undefined;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Version subcommands accept both `<document>` (project from --project/config) and the
|
|
31
|
+
* explicit `<project> <document>` form. `trailing` counts the args that follow the document.
|
|
32
|
+
*
|
|
33
|
+
* @param {string[]} rest - positionals after the subcommand
|
|
34
|
+
* @param {number} trailing
|
|
35
|
+
* @returns {{ projectRef: string | null, docRef: string | undefined, tail: string[] }}
|
|
36
|
+
*/
|
|
37
|
+
function splitVersionRefs(rest, trailing) {
|
|
38
|
+
if (rest.length > 1 + trailing) {
|
|
39
|
+
return { projectRef: rest[0], docRef: rest[1], tail: rest.slice(2) };
|
|
40
|
+
}
|
|
41
|
+
return { projectRef: null, docRef: rest[0], tail: rest.slice(1) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function requireVersionNo(value) {
|
|
45
|
+
const parsed = typeof value === 'string' ? parseInt(value, 10) : NaN;
|
|
46
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
47
|
+
fail('<version-no> must be a positive integer (see: pokelo docs versions)');
|
|
48
|
+
}
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function versionsPath(project, docRef) {
|
|
53
|
+
return `/projects/${encodeURIComponent(project)}/documents/${encodeURIComponent(docRef)}/versions`;
|
|
54
|
+
}
|
|
55
|
+
|
|
29
56
|
/**
|
|
30
57
|
* @param {string[]} positional
|
|
31
58
|
* @param {Record<string, unknown>} flags
|
|
@@ -34,15 +61,66 @@ export async function runDocs(positional, flags) {
|
|
|
34
61
|
const sub = positional[0];
|
|
35
62
|
if (!sub) {
|
|
36
63
|
fail(
|
|
37
|
-
'Usage: pokelo docs <list|get|create|update>\n' +
|
|
64
|
+
'Usage: pokelo docs <list|get|create|update|versions|version|restore>\n' +
|
|
38
65
|
' pokelo docs list [--project <ref>]\n' +
|
|
39
66
|
' pokelo docs get <id-or-slug> [--project <ref>]\n' +
|
|
40
67
|
' pokelo docs create --title <t> [--type] [--content|--content-file] [--parent] [--project]\n' +
|
|
41
|
-
' pokelo docs update <id-or-slug> [--title] [--content|--content-file] [--project]'
|
|
68
|
+
' pokelo docs update <id-or-slug> [--title] [--content|--content-file] [--project]\n' +
|
|
69
|
+
' pokelo docs versions [<project>] <document> [--page <n>] [--limit <n>]\n' +
|
|
70
|
+
' pokelo docs version [<project>] <document> <version-no> [--raw]\n' +
|
|
71
|
+
' pokelo docs restore [<project>] <document> <version-no> [--expect-head <n>] [--include-title]',
|
|
42
72
|
);
|
|
43
73
|
}
|
|
44
74
|
|
|
45
75
|
const config = resolveConfig(flags);
|
|
76
|
+
const rest = positional.slice(1);
|
|
77
|
+
|
|
78
|
+
if (sub === 'versions' || sub === 'version' || sub === 'restore') {
|
|
79
|
+
const trailing = sub === 'versions' ? 0 : 1;
|
|
80
|
+
const { projectRef, docRef, tail } = splitVersionRefs(rest, trailing);
|
|
81
|
+
if (!docRef) {
|
|
82
|
+
fail(
|
|
83
|
+
sub === 'versions'
|
|
84
|
+
? 'Usage: pokelo docs versions [<project>] <document-id-or-slug>'
|
|
85
|
+
: `Usage: pokelo docs ${sub} [<project>] <document-id-or-slug> <version-no>`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const project = projectRef ?? requireProject(config, flags);
|
|
89
|
+
const base = versionsPath(project, docRef);
|
|
90
|
+
|
|
91
|
+
if (sub === 'versions') {
|
|
92
|
+
const data = await apiRequest(config, 'GET', base, {
|
|
93
|
+
query: {
|
|
94
|
+
page: typeof flags.page === 'number' ? flags.page : undefined,
|
|
95
|
+
limit: typeof flags.limit === 'number' ? flags.limit : undefined,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
writeJson(data);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const versionNo = requireVersionNo(tail[0]);
|
|
103
|
+
|
|
104
|
+
if (sub === 'version') {
|
|
105
|
+
const data = await apiRequest(config, 'GET', `${base}/${versionNo}`);
|
|
106
|
+
if (flags.raw === true && flags.json !== true) {
|
|
107
|
+
process.stdout.write(`${data?.contentMd ?? ''}\n`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
writeJson(data);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const body = {};
|
|
115
|
+
if (typeof flags.expectHead === 'number' && Number.isInteger(flags.expectHead)) {
|
|
116
|
+
body.expectedHeadVersionNo = flags.expectHead;
|
|
117
|
+
}
|
|
118
|
+
if (flags.includeTitle === true) body.includeTitle = true;
|
|
119
|
+
const data = await apiRequest(config, 'POST', `${base}/${versionNo}/restore`, { body });
|
|
120
|
+
writeJson(data);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
46
124
|
const project = requireProject(config, flags);
|
|
47
125
|
|
|
48
126
|
if (sub === 'list') {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveConfig } from '../config.mjs';
|
|
1
|
+
import { resolveConfig, resolveWorkspaceId } from '../config.mjs';
|
|
2
2
|
import { fail, writeJson } from '../exit.mjs';
|
|
3
3
|
import { apiRequest } from '../http.mjs';
|
|
4
4
|
|
|
@@ -11,12 +11,17 @@ export async function runProjects(positional, flags) {
|
|
|
11
11
|
const config = resolveConfig(flags);
|
|
12
12
|
|
|
13
13
|
if (sub === 'list' || !sub) {
|
|
14
|
+
const workspaceId =
|
|
15
|
+
(typeof flags.workspace === 'string' && flags.workspace.trim()) ||
|
|
16
|
+
config.defaultWorkspaceId ||
|
|
17
|
+
undefined;
|
|
14
18
|
const data = await apiRequest(config, 'GET', '/projects', {
|
|
15
19
|
query: {
|
|
16
20
|
page: typeof flags.page === 'number' ? flags.page : undefined,
|
|
17
21
|
limit: typeof flags.limit === 'number' ? flags.limit : undefined,
|
|
18
22
|
q: typeof flags.search === 'string' ? flags.search : undefined,
|
|
19
23
|
status: typeof flags.status === 'string' ? flags.status : undefined,
|
|
24
|
+
workspaceId,
|
|
20
25
|
},
|
|
21
26
|
});
|
|
22
27
|
writeJson(data);
|
|
@@ -31,5 +36,19 @@ export async function runProjects(positional, flags) {
|
|
|
31
36
|
return;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
if (sub === 'create') {
|
|
40
|
+
const name = typeof flags.name === 'string' ? flags.name.trim() : '';
|
|
41
|
+
if (!name) fail('Usage: pokelo projects create --name <name> [--workspace <id>]');
|
|
42
|
+
// Order: --workspace / env / config → auto if exactly one membership → error
|
|
43
|
+
const workspaceId = await resolveWorkspaceId(config, apiRequest, { requireWorkspace: true });
|
|
44
|
+
const body = { workspaceId, name };
|
|
45
|
+
if (typeof flags.description === 'string' && flags.description.trim()) {
|
|
46
|
+
body.description = flags.description.trim();
|
|
47
|
+
}
|
|
48
|
+
const data = await apiRequest(config, 'POST', '/projects', { body });
|
|
49
|
+
writeJson(data);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fail(`unknown projects command: ${sub}\nUsage: pokelo projects <list|get|create>`);
|
|
35
54
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { resolveConfig } from '../config.mjs';
|
|
2
|
+
import { fail, writeJson } from '../exit.mjs';
|
|
3
|
+
import { apiRequest } from '../http.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string[]} positional
|
|
7
|
+
* @param {Record<string, unknown>} flags
|
|
8
|
+
*/
|
|
9
|
+
export async function runSearchWorkspace(positional, flags) {
|
|
10
|
+
const query = positional[0];
|
|
11
|
+
if (!query) fail('Usage: pokelo search-workspace "<query>" [--per-project-limit <n>]');
|
|
12
|
+
const config = resolveConfig(flags);
|
|
13
|
+
const perProjectLimit =
|
|
14
|
+
typeof flags['per-project-limit'] === 'number' ? flags['per-project-limit'] : undefined;
|
|
15
|
+
const data = await apiRequest(config, 'POST', '/documents/search-workspace', {
|
|
16
|
+
body: {
|
|
17
|
+
query,
|
|
18
|
+
...(perProjectLimit !== undefined ? { perProjectLimit } : {}),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
writeJson(data);
|
|
22
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { loadPersistedConfig, resolveConfig, saveConfig, xdgConfigPath } from '../config.mjs';
|
|
2
|
+
import { fail, writeJson } from '../exit.mjs';
|
|
3
|
+
import { apiRequest } from '../http.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string[]} positional
|
|
7
|
+
* @param {Record<string, unknown>} flags
|
|
8
|
+
*/
|
|
9
|
+
export async function runWorkspace(positional, flags) {
|
|
10
|
+
const sub = positional[0];
|
|
11
|
+
|
|
12
|
+
if (!sub || sub === 'list') {
|
|
13
|
+
const config = resolveConfig(flags);
|
|
14
|
+
const data = await apiRequest(config, 'GET', '/workspaces');
|
|
15
|
+
writeJson(data);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (sub === 'show') {
|
|
20
|
+
const id = positional[1];
|
|
21
|
+
if (!id) fail('Usage: pokelo workspace show <id>');
|
|
22
|
+
const config = resolveConfig(flags);
|
|
23
|
+
const data = await apiRequest(config, 'GET', `/workspaces/${encodeURIComponent(id)}`);
|
|
24
|
+
writeJson(data);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (sub === 'use') {
|
|
29
|
+
const id = positional[1];
|
|
30
|
+
if (!id) fail('Usage: pokelo workspace use <id>');
|
|
31
|
+
const config = resolveConfig(flags);
|
|
32
|
+
// Verify the workspace is accessible before saving
|
|
33
|
+
const data = await apiRequest(config, 'GET', `/workspaces/${encodeURIComponent(id)}`);
|
|
34
|
+
const existing = loadPersistedConfig();
|
|
35
|
+
const path = saveConfig({
|
|
36
|
+
url: config.url,
|
|
37
|
+
token: config.token,
|
|
38
|
+
defaultProjectId: existing?.defaultProjectId ?? null,
|
|
39
|
+
defaultWorkspaceId: id,
|
|
40
|
+
});
|
|
41
|
+
writeJson({
|
|
42
|
+
ok: true,
|
|
43
|
+
defaultWorkspaceId: id,
|
|
44
|
+
workspaceName: data?.name ?? id,
|
|
45
|
+
configPath: path,
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fail(
|
|
51
|
+
`unknown workspace command: ${sub}\nUsage:\n pokelo workspace list\n pokelo workspace show <id>\n pokelo workspace use <id>`,
|
|
52
|
+
);
|
|
53
|
+
}
|
package/src/config.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { fail } from './exit.mjs';
|
|
|
6
6
|
|
|
7
7
|
const LEGACY_CONFIG_PATH = join(homedir(), '.pokelo-cli.json');
|
|
8
8
|
|
|
9
|
-
/** @typedef {{ url: string, token: string, defaultProjectId?: string | null }} PokeloConfig */
|
|
9
|
+
/** @typedef {{ url: string, token: string, defaultProjectId?: string | null, defaultWorkspaceId?: string | null }} PokeloConfig */
|
|
10
10
|
|
|
11
11
|
export function xdgConfigPath() {
|
|
12
12
|
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
@@ -77,13 +77,17 @@ function sanitizeFileConfig(obj) {
|
|
|
77
77
|
typeof obj.defaultProjectId === 'string' && obj.defaultProjectId.trim()
|
|
78
78
|
? obj.defaultProjectId.trim()
|
|
79
79
|
: null;
|
|
80
|
+
const defaultWorkspaceId =
|
|
81
|
+
typeof obj.defaultWorkspaceId === 'string' && obj.defaultWorkspaceId.trim()
|
|
82
|
+
? obj.defaultWorkspaceId.trim()
|
|
83
|
+
: null;
|
|
80
84
|
if (!url && !token) return null;
|
|
81
|
-
return { url, token, defaultProjectId };
|
|
85
|
+
return { url, token, defaultProjectId, defaultWorkspaceId };
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
/**
|
|
85
89
|
* Resolve runtime config.
|
|
86
|
-
* @param {{ url?: string, token?: string, project?: string }} flags
|
|
90
|
+
* @param {{ url?: string, token?: string, project?: string, workspace?: string }} flags
|
|
87
91
|
*/
|
|
88
92
|
export function resolveConfig(flags = {}) {
|
|
89
93
|
const file = loadPersistedConfig();
|
|
@@ -102,6 +106,11 @@ export function resolveConfig(flags = {}) {
|
|
|
102
106
|
process.env.POKELO_PROJECT?.trim() ||
|
|
103
107
|
file?.defaultProjectId ||
|
|
104
108
|
null;
|
|
109
|
+
const workspace =
|
|
110
|
+
(typeof flags.workspace === 'string' && flags.workspace.trim()) ||
|
|
111
|
+
process.env.POKELO_WORKSPACE?.trim() ||
|
|
112
|
+
file?.defaultWorkspaceId ||
|
|
113
|
+
null;
|
|
105
114
|
|
|
106
115
|
if (!urlRaw) fail('missing Pokelo URL (set --url, POKELO_URL, or run: pokelo auth login)');
|
|
107
116
|
if (!token) fail('missing access token (set --token, POKELO_TOKEN, or run: pokelo auth login)');
|
|
@@ -110,9 +119,37 @@ export function resolveConfig(flags = {}) {
|
|
|
110
119
|
url: normalizeApiBaseUrl(urlRaw),
|
|
111
120
|
token,
|
|
112
121
|
defaultProjectId: project,
|
|
122
|
+
defaultWorkspaceId: workspace,
|
|
113
123
|
};
|
|
114
124
|
}
|
|
115
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Resolve workspace context from flags + config + optional auto-detection.
|
|
128
|
+
* Resolution order: --workspace flag → config defaultWorkspaceId → auto (single membership) → error.
|
|
129
|
+
* @param {{ url: string, token: string, defaultWorkspaceId: string | null }} config
|
|
130
|
+
* @param {import('./http.mjs').apiRequest} apiRequest
|
|
131
|
+
* @param {{ workspace?: string, requireWorkspace?: boolean }} opts
|
|
132
|
+
* @returns {Promise<string | null>}
|
|
133
|
+
*/
|
|
134
|
+
export async function resolveWorkspaceId(config, apiRequest, opts = {}) {
|
|
135
|
+
if (config.defaultWorkspaceId) return config.defaultWorkspaceId;
|
|
136
|
+
if (!opts.requireWorkspace) return null;
|
|
137
|
+
|
|
138
|
+
// Auto-detect: list workspaces and pick if exactly 1 membership
|
|
139
|
+
let data;
|
|
140
|
+
try {
|
|
141
|
+
data = await apiRequest(config, 'GET', '/workspaces');
|
|
142
|
+
} catch {
|
|
143
|
+
fail('workspace_id required: use --workspace, POKELO_WORKSPACE, or: pokelo workspace use <id>');
|
|
144
|
+
}
|
|
145
|
+
const items = Array.isArray(data?.items) ? data.items : [];
|
|
146
|
+
if (items.length === 1) return items[0].id;
|
|
147
|
+
if (items.length === 0) fail('no workspaces found; contact your admin');
|
|
148
|
+
fail(
|
|
149
|
+
`multiple workspaces found — specify one:\n pokelo workspace use <id>\n --workspace <id>\n\nAvailable workspaces:\n${items.map((w) => ` ${w.id} ${w.name}`).join('\n')}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
116
153
|
/**
|
|
117
154
|
* Persist config to XDG path. Never logs the token.
|
|
118
155
|
* @param {{ url: string, token: string, defaultProjectId?: string | null }} cfg
|
|
@@ -124,6 +161,7 @@ export function saveConfig(cfg) {
|
|
|
124
161
|
url: normalizeApiBaseUrl(cfg.url),
|
|
125
162
|
token: String(cfg.token).trim(),
|
|
126
163
|
defaultProjectId: cfg.defaultProjectId?.trim() || null,
|
|
164
|
+
defaultWorkspaceId: cfg.defaultWorkspaceId?.trim() || null,
|
|
127
165
|
};
|
|
128
166
|
writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
129
167
|
try {
|
package/skill/reference.md
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# Pokelo CLI reference
|
|
2
|
-
|
|
3
|
-
## Package vs binary
|
|
4
|
-
|
|
5
|
-
```text
|
|
6
|
-
npm i -g @pokelo/cli → installs package @pokelo/cli
|
|
7
|
-
→ binary name: pokelo
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
## Config priority
|
|
11
|
-
|
|
12
|
-
```text
|
|
13
|
-
--url / --token / --project
|
|
14
|
-
> POKELO_URL / POKELO_TOKEN / POKELO_PROJECT
|
|
15
|
-
> ~/.config/pokelo/config.json
|
|
16
|
-
> legacy ~/.pokelo-cli.json (read for migration on login)
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
URL forms `https://host` and `https://host/v1` both normalize to `…/v1`.
|
|
20
|
-
|
|
21
|
-
## Auth
|
|
22
|
-
|
|
23
|
-
| Command | Notes |
|
|
24
|
-
|---------|--------|
|
|
25
|
-
| `pokelo auth login --url U --token T` | Persist config; never echo T |
|
|
26
|
-
| `pokelo auth login --url U --token-stdin` | Token from stdin |
|
|
27
|
-
| `pokelo auth login` | Interactive TTY only |
|
|
28
|
-
| `pokelo auth status` | Masked preview `mcp_…****` |
|
|
29
|
-
|
|
30
|
-
## Projects / search / docs
|
|
31
|
-
|
|
32
|
-
| Command | REST |
|
|
33
|
-
|---------|------|
|
|
34
|
-
| `projects list` | `GET /projects` |
|
|
35
|
-
| `projects get <ref>` | `GET /projects/:id` |
|
|
36
|
-
| `search "q" -p <ref> -l n` | `POST /projects/:id/documents/search` |
|
|
37
|
-
| `docs list -p <ref>` | `GET /projects/:id/documents` |
|
|
38
|
-
| `docs get <ref> -p <ref>` | `GET /projects/:id/documents/:id` |
|
|
39
|
-
| `docs create --title …` | `POST /projects/:id/documents` |
|
|
40
|
-
| `docs update <ref> …` | `PATCH /projects/:id/documents/:id` |
|
|
41
|
-
|
|
42
|
-
## Assets
|
|
43
|
-
|
|
44
|
-
| Command | REST |
|
|
45
|
-
|---------|------|
|
|
46
|
-
| `assets list` | `GET /projects/:id/assets` |
|
|
47
|
-
| `assets get <id>` | `GET /projects/:id/assets/:id` |
|
|
48
|
-
| `assets upload <file>` | `POST …/base64` or uploads+complete |
|
|
49
|
-
| `assets download <id> --out F` | get + downloadUrl / content redirect |
|
|
50
|
-
| `assets update <id>` | `PATCH /projects/:id/assets/:id` |
|
|
51
|
-
| `assets delete <id>` | `DELETE /projects/:id/assets/:id` |
|
|
52
|
-
|
|
53
|
-
Filters for list: `--type`, `--source`, `--visibility`, `--document`, `--page`, `--limit`.
|
|
54
|
-
|
|
55
|
-
## Safety
|
|
56
|
-
|
|
57
|
-
- Never print the full PAT.
|
|
58
|
-
- Prefer `--token-stdin` or env over `--token` when possible.
|
|
59
|
-
- Do not commit `~/.config/pokelo/config.json`.
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Ensure the `pokelo` binary is on PATH. No-op if already available.
|
|
3
|
-
# npm package name: @pokelo/cli → CLI command: pokelo
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
if command -v pokelo >/dev/null 2>&1; then
|
|
7
|
-
echo "pokelo already available: $(command -v pokelo)"
|
|
8
|
-
exit 0
|
|
9
|
-
fi
|
|
10
|
-
|
|
11
|
-
if command -v npm >/dev/null 2>&1; then
|
|
12
|
-
echo "Installing @pokelo/cli (provides \`pokelo\` binary)…"
|
|
13
|
-
if npm install -g @pokelo/cli; then
|
|
14
|
-
if command -v pokelo >/dev/null 2>&1; then
|
|
15
|
-
echo "Installed: $(command -v pokelo)"
|
|
16
|
-
exit 0
|
|
17
|
-
fi
|
|
18
|
-
echo "Package installed but \`pokelo\` not on PATH; check npm global bin." >&2
|
|
19
|
-
else
|
|
20
|
-
echo "npm install -g @pokelo/cli failed (package may be unpublished)." >&2
|
|
21
|
-
fi
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
# Monorepo / package-local fallbacks
|
|
25
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
-
CANDIDATES=(
|
|
27
|
-
# skill shipped inside tools/pokelo-cli/skill/scripts
|
|
28
|
-
"$SCRIPT_DIR/../../bin/pokelo.mjs"
|
|
29
|
-
# .cursor/skills/pokelo-cli/scripts → repo tools/pokelo-cli
|
|
30
|
-
"$SCRIPT_DIR/../../../tools/pokelo-cli/bin/pokelo.mjs"
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
mkdir -p "${HOME}/.local/bin"
|
|
34
|
-
for candidate in "${CANDIDATES[@]}"; do
|
|
35
|
-
if [[ -f "$candidate" ]]; then
|
|
36
|
-
ln -sfn "$candidate" "${HOME}/.local/bin/pokelo"
|
|
37
|
-
export PATH="${HOME}/.local/bin:${PATH}"
|
|
38
|
-
if command -v pokelo >/dev/null 2>&1; then
|
|
39
|
-
echo "Linked local CLI: $candidate → ~/.local/bin/pokelo"
|
|
40
|
-
exit 0
|
|
41
|
-
fi
|
|
42
|
-
fi
|
|
43
|
-
done
|
|
44
|
-
|
|
45
|
-
echo "Error: could not locate or install pokelo." >&2
|
|
46
|
-
echo "Fix: npm install -g @pokelo/cli OR symlink tools/pokelo-cli/bin/pokelo.mjs" >&2
|
|
47
|
-
exit 1
|