emberflow-skills 1.2.0 → 1.3.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/bin/install.js +2 -2
- package/package.json +1 -1
- package/skills/ember-publish-space/SKILL.md +211 -0
package/bin/install.js
CHANGED
|
@@ -7,7 +7,7 @@ const http = require('http');
|
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
const os = require('os');
|
|
9
9
|
|
|
10
|
-
const SKILL_NAMES = ['ember-publish', 'ember-publish-json'];
|
|
10
|
+
const SKILL_NAMES = ['ember-publish', 'ember-publish-json', 'ember-publish-space'];
|
|
11
11
|
const SKILLS_DIR = path.join(__dirname, '..', 'skills');
|
|
12
12
|
const EMBERFLOW_URL = 'https://www.emberflow.ai';
|
|
13
13
|
const TOKEN_PATH = path.join(os.homedir(), '.emberflow', 'token.json');
|
|
@@ -227,7 +227,7 @@ async function main() {
|
|
|
227
227
|
|
|
228
228
|
if (installed > 0) {
|
|
229
229
|
console.log();
|
|
230
|
-
console.log(` Use: ${cyan('/ember-publish')} ${dim('[topic]')} or ${cyan('/ember-publish-json')} ${dim('[data]')}`);
|
|
230
|
+
console.log(` Use: ${cyan('/ember-publish')} ${dim('[topic]')} or ${cyan('/ember-publish-json')} ${dim('[data]')} or ${cyan('/ember-publish-space')} ${dim('[directory]')}`);
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ember-publish-space
|
|
3
|
+
description: Publish a directory of markdown files as an Emberflow Space — a multi-page docs site with sidebar navigation
|
|
4
|
+
argument-hint: [directory path or file list]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Emberflow Space Publisher
|
|
8
|
+
|
|
9
|
+
Publish a collection of markdown files as an **Emberflow Space** — a multi-page docs site at **https://emberflow.ai** with sidebar navigation, Mermaid diagram rendering, dark mode, and per-block commenting.
|
|
10
|
+
|
|
11
|
+
## Step 1: Collect Files
|
|
12
|
+
|
|
13
|
+
Accept a directory path or explicit file list from the user. Scan for `.md` files:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Example: find all markdown files in a directory
|
|
17
|
+
DOCS_DIR="/path/to/docs"
|
|
18
|
+
find "$DOCS_DIR" -name "*.md" -type f | sort
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For each `.md` file:
|
|
22
|
+
- Extract the title from the first `# Title` line
|
|
23
|
+
- Generate a slug from the title: lowercase, replace non-alphanumeric runs with `-`, trim leading/trailing `-`
|
|
24
|
+
- Note the parent directory name (if in a subdirectory) — this becomes a nav section
|
|
25
|
+
|
|
26
|
+
### Directory Structure Mapping
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
docs/
|
|
30
|
+
getting-started.md → top-level page
|
|
31
|
+
api-reference.md → top-level page
|
|
32
|
+
guides/
|
|
33
|
+
authentication.md → page under "Guides" section
|
|
34
|
+
deployment.md → page under "Guides" section
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Step 2: Authenticate (if needed)
|
|
38
|
+
|
|
39
|
+
Session tokens are stored at `~/.emberflow/token.json`. Check if a valid session exists:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cat ~/.emberflow/token.json 2>/dev/null
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If the file exists, verify the token still works:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
curl -s -H "Authorization: Bearer $(jq -r .token ~/.emberflow/token.json)" \
|
|
49
|
+
https://emberflow.ai/api/docs
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
If no session exists, it's expired, or the verify call returns 401, authenticate using the device flow:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
EMBERFLOW_URL="https://emberflow.ai"
|
|
56
|
+
|
|
57
|
+
# Step 1: Request a device code
|
|
58
|
+
RESP=$(curl -s -X POST "$EMBERFLOW_URL/api/device-code")
|
|
59
|
+
CODE=$(echo "$RESP" | jq -r .code)
|
|
60
|
+
URL=$(echo "$RESP" | jq -r .verification_url)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Tell the user to open the URL in their browser to sign in and approve the device. Then poll until approved:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Step 2: Poll until approved (every 3s)
|
|
67
|
+
while true; do
|
|
68
|
+
STATUS=$(curl -s "$EMBERFLOW_URL/api/device-code/$CODE")
|
|
69
|
+
S=$(echo "$STATUS" | jq -r .status)
|
|
70
|
+
if [ "$S" = "approved" ]; then
|
|
71
|
+
TOKEN=$(echo "$STATUS" | jq -r .session_token)
|
|
72
|
+
mkdir -p ~/.emberflow
|
|
73
|
+
echo "{\"token\":\"$TOKEN\"}" > ~/.emberflow/token.json
|
|
74
|
+
break
|
|
75
|
+
fi
|
|
76
|
+
if [ "$S" = "expired" ]; then
|
|
77
|
+
echo "Code expired. Please try again."
|
|
78
|
+
break
|
|
79
|
+
fi
|
|
80
|
+
sleep 3
|
|
81
|
+
done
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Step 3: Create or Select Space
|
|
85
|
+
|
|
86
|
+
First, check if the user wants to publish to an existing space or create a new one:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
TOKEN=$(jq -r .token ~/.emberflow/token.json)
|
|
90
|
+
EMBERFLOW_URL="https://emberflow.ai"
|
|
91
|
+
|
|
92
|
+
# List existing spaces
|
|
93
|
+
curl -s -H "Authorization: Bearer $TOKEN" "$EMBERFLOW_URL/api/spaces"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If creating a new space:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Generate name and slug from directory name or user input
|
|
100
|
+
SPACE_NAME="My Docs"
|
|
101
|
+
SPACE_SLUG=$(echo "$SPACE_NAME" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-//;s/-$//')
|
|
102
|
+
|
|
103
|
+
SPACE_RESP=$(curl -s -X POST "$EMBERFLOW_URL/api/spaces" \
|
|
104
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
105
|
+
-H "Content-Type: application/json" \
|
|
106
|
+
-d "$(jq -n --arg name "$SPACE_NAME" --arg slug "$SPACE_SLUG" \
|
|
107
|
+
'{name: $name, slug: $slug}')")
|
|
108
|
+
|
|
109
|
+
SPACE_ID=$(echo "$SPACE_RESP" | jq -r .id)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
If using an existing space, extract its `id` from the list response.
|
|
113
|
+
|
|
114
|
+
## Step 4: Build Payload and Batch Publish
|
|
115
|
+
|
|
116
|
+
Build the `documents` and `nav` arrays, then call the batch publish endpoint.
|
|
117
|
+
|
|
118
|
+
The API expects:
|
|
119
|
+
- `documents[]`: array of `{ slug, title, content }` — each doc is upserted by slug
|
|
120
|
+
- `nav[]`: array of nav items:
|
|
121
|
+
- Sections: `{ type: "section", label: "Section Name" }`
|
|
122
|
+
- Pages: `{ type: "page", label: "Page Title", slug: "page-slug" }` — top-level
|
|
123
|
+
- Pages in sections: `{ type: "page", label: "Page Title", slug: "page-slug", parent_label: "Section Name" }`
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
TOKEN=$(jq -r .token ~/.emberflow/token.json)
|
|
127
|
+
EMBERFLOW_URL="https://emberflow.ai"
|
|
128
|
+
DOCS_DIR="/path/to/docs"
|
|
129
|
+
|
|
130
|
+
# Build the JSON payload with jq
|
|
131
|
+
# For each .md file: read content, extract title, generate slug, determine section
|
|
132
|
+
|
|
133
|
+
DOCUMENTS="[]"
|
|
134
|
+
NAV="[]"
|
|
135
|
+
SECTIONS_ADDED=""
|
|
136
|
+
|
|
137
|
+
for file in $(find "$DOCS_DIR" -name "*.md" -type f | sort); do
|
|
138
|
+
# Extract title and slug
|
|
139
|
+
TITLE=$(head -1 "$file" | sed 's/^#\s*//')
|
|
140
|
+
SLUG=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-//;s/-$//')
|
|
141
|
+
CONTENT=$(cat "$file")
|
|
142
|
+
|
|
143
|
+
# Determine if in a subdirectory (becomes a section)
|
|
144
|
+
REL_PATH=$(realpath --relative-to="$DOCS_DIR" "$file")
|
|
145
|
+
DIR_NAME=$(dirname "$REL_PATH")
|
|
146
|
+
|
|
147
|
+
# Add document
|
|
148
|
+
DOCUMENTS=$(echo "$DOCUMENTS" | jq --arg slug "$SLUG" --arg title "$TITLE" --rawfile content "$file" \
|
|
149
|
+
'. + [{slug: $slug, title: $title, content: $content}]')
|
|
150
|
+
|
|
151
|
+
if [ "$DIR_NAME" = "." ]; then
|
|
152
|
+
# Top-level page
|
|
153
|
+
NAV=$(echo "$NAV" | jq --arg label "$TITLE" --arg slug "$SLUG" \
|
|
154
|
+
'. + [{type: "page", label: $label, slug: $slug}]')
|
|
155
|
+
else
|
|
156
|
+
# Page under a section
|
|
157
|
+
SECTION_LABEL=$(echo "$DIR_NAME" | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g')
|
|
158
|
+
|
|
159
|
+
# Add section if not already added
|
|
160
|
+
if ! echo "$SECTIONS_ADDED" | grep -qF "$SECTION_LABEL"; then
|
|
161
|
+
NAV=$(echo "$NAV" | jq --arg label "$SECTION_LABEL" \
|
|
162
|
+
'. + [{type: "section", label: $label}]')
|
|
163
|
+
SECTIONS_ADDED="$SECTIONS_ADDED|$SECTION_LABEL"
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
NAV=$(echo "$NAV" | jq --arg label "$TITLE" --arg slug "$SLUG" --arg parent "$SECTION_LABEL" \
|
|
167
|
+
'. + [{type: "page", label: $label, slug: $slug, parent_label: $parent}]')
|
|
168
|
+
fi
|
|
169
|
+
done
|
|
170
|
+
|
|
171
|
+
# Write payload to temp file (handles large doc sets)
|
|
172
|
+
jq -n --argjson documents "$DOCUMENTS" --argjson nav "$NAV" \
|
|
173
|
+
'{documents: $documents, nav: $nav}' > /tmp/emberflow-publish.json
|
|
174
|
+
|
|
175
|
+
# Publish
|
|
176
|
+
RESULT=$(curl -s -X POST "$EMBERFLOW_URL/api/spaces/$SPACE_ID/publish" \
|
|
177
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
178
|
+
-H "Content-Type: application/json" \
|
|
179
|
+
-d @/tmp/emberflow-publish.json)
|
|
180
|
+
|
|
181
|
+
echo "$RESULT"
|
|
182
|
+
rm -f /tmp/emberflow-publish.json
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Step 5: Output Result
|
|
186
|
+
|
|
187
|
+
The API response includes:
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"published": true,
|
|
191
|
+
"url": "https://emberflow.ai/s/<author_short_id>/<space_slug>",
|
|
192
|
+
"document_count": 5
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Show the user:
|
|
197
|
+
- The Space URL
|
|
198
|
+
- Number of documents published
|
|
199
|
+
- The nav structure that was created
|
|
200
|
+
|
|
201
|
+
To **update** an existing space, publish again to the same space — documents are upserted by slug, and nav items are recreated.
|
|
202
|
+
|
|
203
|
+
### Other Operations
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# List your spaces
|
|
207
|
+
curl -s -H "Authorization: Bearer $TOKEN" "$EMBERFLOW_URL/api/spaces"
|
|
208
|
+
|
|
209
|
+
# Delete a space
|
|
210
|
+
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$EMBERFLOW_URL/api/spaces/SPACE_ID_HERE"
|
|
211
|
+
```
|