@codio-ai/opencode-authoring-agent 0.1.0 → 0.1.2
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/codio-authoring-register.js +23 -1
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/page-author.d.ts.map +1 -1
- package/dist/codio-scripts/codio-fetch.js +45931 -0
- package/dist/codio-scripts/codio-publish.js +44984 -0
- package/dist/index.js +225 -6
- package/dist/tools/create-page.d.ts +1 -0
- package/dist/tools/create-page.d.ts.map +1 -1
- package/dist/tools/validate-guide.d.ts +1 -1
- package/dist/tools/validate-guide.d.ts.map +1 -1
- package/package.json +9 -19
- package/src/skills/codio-fetch/SKILL.md +153 -0
- package/src/skills/codio-fetch/scripts/extract-course-export.sh +69 -0
- package/src/skills/codio-fetch/scripts/run-course-export.sh +196 -0
- package/src/skills/codio-publish/SKILL.md +140 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -Eeuo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
FETCH_JS="$SCRIPT_DIR/codio-fetch.js"
|
|
6
|
+
|
|
7
|
+
# Walk up from CWD to find .env; load it when found so credentials override env.
|
|
8
|
+
# .env lives at the workspace root and is shared across all Codio skills.
|
|
9
|
+
find_and_load_env() {
|
|
10
|
+
local dir
|
|
11
|
+
dir="$(pwd)"
|
|
12
|
+
while [[ "$dir" != "/" ]]; do
|
|
13
|
+
if [[ -f "$dir/.env" ]]; then
|
|
14
|
+
set -a
|
|
15
|
+
# shellcheck disable=SC1090
|
|
16
|
+
. "$dir/.env"
|
|
17
|
+
set +a
|
|
18
|
+
return 0
|
|
19
|
+
fi
|
|
20
|
+
dir="$(dirname "$dir")"
|
|
21
|
+
done
|
|
22
|
+
return 0 # .env is optional; continue without it
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
find_and_load_env
|
|
26
|
+
|
|
27
|
+
OUTPUT_DIR=""
|
|
28
|
+
COURSE_ID="${CODIO_COURSE_ID:-}"
|
|
29
|
+
DOMAIN="${CODIO_DOMAIN:-codio.com}"
|
|
30
|
+
|
|
31
|
+
log() {
|
|
32
|
+
printf '[%s] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fail() {
|
|
36
|
+
log "ERROR: $*"
|
|
37
|
+
exit 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
usage() {
|
|
41
|
+
cat <<'USAGE'
|
|
42
|
+
Usage:
|
|
43
|
+
./run-course-export.sh --course-id <id> [--output-dir <path>] [--domain <domain>]
|
|
44
|
+
CODIO_COURSE_ID=<id> ./run-course-export.sh
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
--course-id <id> Codio course ID (overrides CODIO_COURSE_ID from .env)
|
|
48
|
+
--output-dir <path> Where to write the project folder (default: ./course-project)
|
|
49
|
+
--domain <domain> Codio domain (default: codio.com or CODIO_DOMAIN from .env)
|
|
50
|
+
|
|
51
|
+
Credentials (loaded from .env at workspace root):
|
|
52
|
+
CODIO_CLIENT_ID Required
|
|
53
|
+
CODIO_CLIENT_SECRET Required
|
|
54
|
+
CODIO_COURSE_ID Optional — used when --course-id is not supplied
|
|
55
|
+
CODIO_DOMAIN Optional — used when --domain is not supplied
|
|
56
|
+
USAGE
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
require_command() {
|
|
60
|
+
command -v "$1" >/dev/null 2>&1 || fail "Required command not found: $1"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
require_env() {
|
|
64
|
+
local name="$1"
|
|
65
|
+
[[ -n "${!name:-}" ]] || fail "Missing required environment variable: ${name}"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
parse_args() {
|
|
69
|
+
while (($# > 0)); do
|
|
70
|
+
case "$1" in
|
|
71
|
+
--course-id)
|
|
72
|
+
[[ -n "${2:-}" ]] || fail 'Missing value for --course-id'
|
|
73
|
+
COURSE_ID="$2"; shift 2 ;;
|
|
74
|
+
--course-id=*)
|
|
75
|
+
COURSE_ID="${1#--course-id=}"; shift ;;
|
|
76
|
+
--output-dir)
|
|
77
|
+
[[ -n "${2:-}" ]] || fail 'Missing value for --output-dir'
|
|
78
|
+
OUTPUT_DIR="$2"; shift 2 ;;
|
|
79
|
+
--output-dir=*)
|
|
80
|
+
OUTPUT_DIR="${1#--output-dir=}"; shift ;;
|
|
81
|
+
--domain)
|
|
82
|
+
[[ -n "${2:-}" ]] || fail 'Missing value for --domain'
|
|
83
|
+
DOMAIN="$2"; shift 2 ;;
|
|
84
|
+
--domain=*)
|
|
85
|
+
DOMAIN="${1#--domain=}"; shift ;;
|
|
86
|
+
-h|--help)
|
|
87
|
+
usage; exit 0 ;;
|
|
88
|
+
--*)
|
|
89
|
+
fail "Unknown option: $1" ;;
|
|
90
|
+
*)
|
|
91
|
+
[[ -z "$COURSE_ID" ]] || fail 'Course ID was provided more than once'
|
|
92
|
+
COURSE_ID="$1"; shift ;;
|
|
93
|
+
esac
|
|
94
|
+
done
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
validate_manifest_paths() {
|
|
98
|
+
node - <<'NODE' "$VISIBLE_MANIFEST_JSON"
|
|
99
|
+
const fs = require('node:fs');
|
|
100
|
+
const path = require('node:path');
|
|
101
|
+
|
|
102
|
+
const manifestPath = process.argv[2];
|
|
103
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
104
|
+
const root = path.dirname(manifestPath);
|
|
105
|
+
const missing = [];
|
|
106
|
+
|
|
107
|
+
for (const module of manifest.modules ?? []) {
|
|
108
|
+
const modulePath = path.join(root, module.path ?? '');
|
|
109
|
+
if (!fs.existsSync(modulePath) || !fs.statSync(modulePath).isDirectory()) {
|
|
110
|
+
missing.push(module.path ?? '');
|
|
111
|
+
}
|
|
112
|
+
for (const assignment of module.assignments ?? []) {
|
|
113
|
+
const assignmentPath = path.join(root, assignment.path ?? '');
|
|
114
|
+
if (!fs.existsSync(assignmentPath) || !fs.statSync(assignmentPath).isDirectory()) {
|
|
115
|
+
missing.push(assignment.path ?? '');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (missing.length > 0) {
|
|
121
|
+
console.error('[manifest] WARNING: The following planned content paths were not found after extraction:');
|
|
122
|
+
for (const missingPath of missing) {
|
|
123
|
+
console.error(`[manifest] - ${missingPath}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
NODE
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
main() {
|
|
130
|
+
parse_args "$@"
|
|
131
|
+
|
|
132
|
+
require_command node
|
|
133
|
+
require_command unzip
|
|
134
|
+
require_command tar
|
|
135
|
+
require_command find
|
|
136
|
+
|
|
137
|
+
require_env CODIO_CLIENT_ID
|
|
138
|
+
require_env CODIO_CLIENT_SECRET
|
|
139
|
+
[[ -n "$COURSE_ID" ]] || fail 'Missing course id. Pass --course-id <id>, pass a positional id, or set CODIO_COURSE_ID in .env.'
|
|
140
|
+
|
|
141
|
+
[[ -f "$FETCH_JS" ]] || fail "codio-fetch.js not found at $FETCH_JS — reinstall the plugin."
|
|
142
|
+
|
|
143
|
+
# Resolve output directory to an absolute path
|
|
144
|
+
if [[ -z "$OUTPUT_DIR" ]]; then
|
|
145
|
+
OUTPUT_DIR="$(pwd)/course-project"
|
|
146
|
+
elif [[ "$OUTPUT_DIR" != /* ]]; then
|
|
147
|
+
OUTPUT_DIR="$(pwd)/$OUTPUT_DIR"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
VISIBLE_OUTPUT_DIR="$OUTPUT_DIR"
|
|
151
|
+
VISIBLE_CONTENT_DIR="$VISIBLE_OUTPUT_DIR/course-content"
|
|
152
|
+
VISIBLE_ASSIGNMENTS_CSV="$VISIBLE_OUTPUT_DIR/course-assignments.csv"
|
|
153
|
+
VISIBLE_MANIFEST_JSON="$VISIBLE_OUTPUT_DIR/course-manifest.json"
|
|
154
|
+
|
|
155
|
+
RUN_TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
156
|
+
EXPORT_BASENAME="course-source-export-${RUN_TIMESTAMP}"
|
|
157
|
+
HIDDEN_WORK_DIR="$(mktemp -d)"
|
|
158
|
+
DOWNLOADS_DIR="$HIDDEN_WORK_DIR/downloads"
|
|
159
|
+
STAGING_DIR="$HIDDEN_WORK_DIR/staging"
|
|
160
|
+
EXPORT_ZIP_PATH="$DOWNLOADS_DIR/${EXPORT_BASENAME}.zip"
|
|
161
|
+
STAGING_EXTRACT_DIR="$STAGING_DIR/${EXPORT_BASENAME}"
|
|
162
|
+
|
|
163
|
+
rm -rf "$VISIBLE_OUTPUT_DIR"
|
|
164
|
+
mkdir -p "$VISIBLE_CONTENT_DIR" "$DOWNLOADS_DIR" "$STAGING_DIR"
|
|
165
|
+
|
|
166
|
+
log "Generating AI-ready course project"
|
|
167
|
+
log "Course ID: ${COURSE_ID}"
|
|
168
|
+
log "Domain: ${DOMAIN}"
|
|
169
|
+
log "Output: ${VISIBLE_OUTPUT_DIR}"
|
|
170
|
+
|
|
171
|
+
node "$FETCH_JS" \
|
|
172
|
+
--course-id "$COURSE_ID" \
|
|
173
|
+
--client-id "$CODIO_CLIENT_ID" \
|
|
174
|
+
--client-secret "$CODIO_CLIENT_SECRET" \
|
|
175
|
+
--domain "$DOMAIN" \
|
|
176
|
+
--export-zip-path "$EXPORT_ZIP_PATH" \
|
|
177
|
+
--assignments-csv-path "$VISIBLE_ASSIGNMENTS_CSV" \
|
|
178
|
+
--manifest-path "$VISIBLE_MANIFEST_JSON" \
|
|
179
|
+
--course-content-path "$VISIBLE_CONTENT_DIR"
|
|
180
|
+
|
|
181
|
+
"$SCRIPT_DIR/extract-course-export.sh" \
|
|
182
|
+
"$EXPORT_ZIP_PATH" \
|
|
183
|
+
"$STAGING_EXTRACT_DIR" \
|
|
184
|
+
"$VISIBLE_CONTENT_DIR"
|
|
185
|
+
|
|
186
|
+
# Remove exported README.md files (Codio export artefacts, not assignment content)
|
|
187
|
+
find "$VISIBLE_OUTPUT_DIR" -type f -iname 'README.md' -delete
|
|
188
|
+
|
|
189
|
+
validate_manifest_paths
|
|
190
|
+
|
|
191
|
+
rm -rf "$HIDDEN_WORK_DIR"
|
|
192
|
+
|
|
193
|
+
log "Done. Project available at ${VISIBLE_OUTPUT_DIR}"
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
main "$@"
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: codio-publish
|
|
3
|
+
description: Publish Codio course assignments to the Codio platform. Use when the user asks to publish, deploy, or upload assignments to Codio, or to sync course content with a Codio course. Reads course-manifest.json and creates courses, modules, and assignments as needed before publishing each one.
|
|
4
|
+
argument-hint: [--force] [--module <name>] [--assignment <name>] [--dry-run] [--changelog <text>] [--course-name <name>] [--stack <stack-id>]
|
|
5
|
+
allowed-tools: [Bash, Read, Write, Edit]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Codio Publish
|
|
9
|
+
|
|
10
|
+
Publish course assignments to Codio using `course-manifest.json` as the source of truth.
|
|
11
|
+
|
|
12
|
+
## What this skill does
|
|
13
|
+
|
|
14
|
+
1. Loads credentials from `.env` (walks up from CWD to find it), overriding any existing env vars.
|
|
15
|
+
2. Reads `course-manifest.json` from the current working directory (the project folder).
|
|
16
|
+
3. Creates a Codio course if `courseId` is missing.
|
|
17
|
+
4. For each module: resolves or creates the module in Codio and records its ID.
|
|
18
|
+
5. For each assignment: creates it in Codio if the `id` field is empty, then publishes it.
|
|
19
|
+
- New assignments (no prior ID) are published with the default stack.
|
|
20
|
+
- Existing assignments (already have an ID) are published without a stack unless `--stack` is passed.
|
|
21
|
+
6. Writes back any new IDs to `course-manifest.json` immediately after each resource is created.
|
|
22
|
+
|
|
23
|
+
## Project folder layout
|
|
24
|
+
|
|
25
|
+
`course-manifest.json` and `course-content/` must live together inside a named project subfolder. The manifest must **not** be placed at the workspace root alone.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
workspace/ ← workspace root (contains .env)
|
|
29
|
+
└── my-course/ ← project folder — cd here before running
|
|
30
|
+
├── course-manifest.json
|
|
31
|
+
└── course-content/
|
|
32
|
+
└── <module-folder>/
|
|
33
|
+
└── <assignment-folder>/
|
|
34
|
+
└── .guides/ ← or a .zip archive
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Run the publish script from inside the project folder:
|
|
38
|
+
```bash
|
|
39
|
+
cd my-course
|
|
40
|
+
node ~/.config/opencode/skills/codio-publish/scripts/codio-publish.js
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Prerequisites
|
|
44
|
+
|
|
45
|
+
Before running, verify:
|
|
46
|
+
|
|
47
|
+
1. **`.env` file** — must exist at the workspace root (the script walks up from CWD to find it):
|
|
48
|
+
```
|
|
49
|
+
CODIO_CLIENT_ID=your-client-id
|
|
50
|
+
CODIO_CLIENT_SECRET=your-client-secret
|
|
51
|
+
CODIO_DOMAIN=codio.com # optional, defaults to codio.com
|
|
52
|
+
```
|
|
53
|
+
The `.env` is shared across all Codio skills — one file at the workspace root, not one per skill.
|
|
54
|
+
|
|
55
|
+
Check credentials are loaded:
|
|
56
|
+
```bash
|
|
57
|
+
node ~/.config/opencode/skills/codio-publish/scripts/codio-publish.js --dry-run
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. **`course-manifest.json`** — must exist in the directory you run from (the project folder). If not found, tell the user.
|
|
61
|
+
|
|
62
|
+
## Running the publish script
|
|
63
|
+
|
|
64
|
+
Run from the project folder (the directory that contains `course-manifest.json`):
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node ~/.config/opencode/skills/codio-publish/scripts/codio-publish.js [options]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Options
|
|
71
|
+
|
|
72
|
+
| Option | Description |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `--force` | Publish all assignments regardless of whether content has changed |
|
|
75
|
+
| `--dry-run` | Preview all actions without making any changes |
|
|
76
|
+
| `--module <name>` | Only process assignments in this module (name or folder) |
|
|
77
|
+
| `--assignment <name>` | Only process this assignment (name or folder) |
|
|
78
|
+
| `--changelog <text>` | Changelog message applied to all publishes |
|
|
79
|
+
| `--course-name <name>` | Course name to use when creating a new course |
|
|
80
|
+
| `--stack <stack-id>` | Force a specific stack for all assignments in this run (overrides default) |
|
|
81
|
+
|
|
82
|
+
### Example: dry run first, then publish
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
node ~/.config/opencode/skills/codio-publish/scripts/codio-publish.js --dry-run
|
|
86
|
+
node ~/.config/opencode/skills/codio-publish/scripts/codio-publish.js
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Example: publish one module
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
node ~/.config/opencode/skills/codio-publish/scripts/codio-publish.js --module "Basic Skills"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Example: publish one assignment
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
node ~/.config/opencode/skills/codio-publish/scripts/codio-publish.js --assignment "Printing"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Example: publish with a custom stack
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
node ~/.config/opencode/skills/codio-publish/scripts/codio-publish.js --stack e0195698-d647-4490-8834-350583b532eb:latest
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Environment variables
|
|
108
|
+
|
|
109
|
+
| Variable | Required | Description |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `CODIO_CLIENT_ID` | Yes | Codio API client ID |
|
|
112
|
+
| `CODIO_CLIENT_SECRET` | Yes | Codio API client secret |
|
|
113
|
+
| `CODIO_DOMAIN` | No | Codio domain, default `codio.com` |
|
|
114
|
+
|
|
115
|
+
## Assignment content
|
|
116
|
+
|
|
117
|
+
The script looks for content in the `path` field of each assignment in the manifest (e.g. `course-content/basic-skills/printing`). It handles two layouts automatically:
|
|
118
|
+
|
|
119
|
+
- **Zip archive** — the folder contains a `.zip` file. Published via `publishArchive`.
|
|
120
|
+
- **Extracted guide** — the folder contains a `.guides/` directory or other files. Published via `publish`.
|
|
121
|
+
|
|
122
|
+
## Stacks
|
|
123
|
+
|
|
124
|
+
- **New assignments** (no `id` in the manifest) are always published with the default stack.
|
|
125
|
+
- **Existing assignments** (already have an `id`) are published without a stack by default.
|
|
126
|
+
- **`--stack <stack-id>`** forces a specific stack for all assignments in the current run.
|
|
127
|
+
|
|
128
|
+
## Change detection (default behaviour)
|
|
129
|
+
|
|
130
|
+
The script skips existing assignments whose content has not changed since the last publish. Change detection uses a SHA-256 fingerprint stored as `contentHash` in `course-manifest.json`. Pass `--force` to bypass it.
|
|
131
|
+
|
|
132
|
+
## Troubleshooting
|
|
133
|
+
|
|
134
|
+
| Error | Fix |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `CODIO_CLIENT_ID ... must be set` | Create or fix `.env` at workspace root |
|
|
137
|
+
| `course-manifest.json not found` | Run from the project folder, not the workspace root |
|
|
138
|
+
| `path not found` for an assignment | The folder referenced in the manifest doesn't exist — generate or place content there first |
|
|
139
|
+
| API 401 / auth error | Credentials in `.env` are invalid or expired |
|
|
140
|
+
| API 429 rate limit | The library retries automatically; wait and re-run if it still fails |
|