@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.
@@ -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 |