@codio-ai/opencode-authoring-agent 0.1.0 → 0.1.3

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.
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ export declare function validateFreezeDirectives(markdownBody: string): string[];
2
3
  export declare const PAGE_TYPES: readonly ["intro", "objectives", "instructional", "assessment", "conclusion"];
3
4
  export declare const PAGE_LAYOUTS: readonly ["1-panel", "2-panels-tree"];
4
5
  export declare const createPageInputSchema: z.ZodObject<{
@@ -1 +1 @@
1
- {"version":3,"file":"create-page.d.ts","sourceRoot":"","sources":["../../src/tools/create-page.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,UAAU,+EAMb,CAAC;AAEX,eAAO,MAAM,YAAY,uCAAwC,CAAC;AAElE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;iBAuB/B,CAAC;AAEJ,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,CAAC;IACxB,MAAM,EAAE,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;IACtC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,QAAQ,CAgB9D;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,KAAK,EAAE,eAAe,GACrB,gBAAgB,CA6BlB;AAED,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,gBAAgB,CAAC,CAG3B"}
1
+ {"version":3,"file":"create-page.d.ts","sourceRoot":"","sources":["../../src/tools/create-page.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CA6BvE;AAED,eAAO,MAAM,UAAU,+EAMb,CAAC;AAEX,eAAO,MAAM,YAAY,uCAAwC,CAAC;AAElE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;iBAuB/B,CAAC;AAEJ,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,CAAC;IACxB,MAAM,EAAE,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;IACtC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,QAAQ,CAgB9D;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,KAAK,EAAE,eAAe,GACrB,gBAAgB,CA6BlB;AAED,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,gBAAgB,CAAC,CAO3B"}
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ export declare const fetchCourseInputSchema: z.ZodObject<{
3
+ workspace: z.ZodString;
4
+ courseId: z.ZodString;
5
+ outputDir: z.ZodOptional<z.ZodString>;
6
+ domain: z.ZodOptional<z.ZodString>;
7
+ }, z.core.$strip>;
8
+ export type FetchCourseInput = z.infer<typeof fetchCourseInputSchema>;
9
+ export interface FetchCourseResult {
10
+ outputDir: string;
11
+ manifestPath: string;
12
+ assignmentsCsvPath: string;
13
+ moduleCount: number;
14
+ assignmentCount: number;
15
+ }
16
+ export declare function fetchCourseHandler(raw: unknown): Promise<FetchCourseResult>;
17
+ //# sourceMappingURL=fetch-course.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-course.d.ts","sourceRoot":"","sources":["../../src/tools/fetch-course.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,sBAAsB;;;;;iBAQjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAoED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoGjF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fetch-course.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-course.test.d.ts","sourceRoot":"","sources":["../../src/tools/fetch-course.test.ts"],"names":[],"mappings":""}
@@ -58,5 +58,41 @@ export declare const validate_guide: {
58
58
  workspace: string;
59
59
  }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
60
60
  };
61
+ export declare const fetch_course: {
62
+ description: string;
63
+ args: {
64
+ workspace: import("zod").ZodString;
65
+ courseId: import("zod").ZodString;
66
+ outputDir: import("zod").ZodOptional<import("zod").ZodString>;
67
+ domain: import("zod").ZodOptional<import("zod").ZodString>;
68
+ };
69
+ execute(args: {
70
+ workspace: string;
71
+ courseId: string;
72
+ outputDir?: string | undefined;
73
+ domain?: string | undefined;
74
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
75
+ };
76
+ export declare const publish_course: {
77
+ description: string;
78
+ args: {
79
+ manifestPath: import("zod").ZodString;
80
+ dryRun: import("zod").ZodOptional<import("zod").ZodBoolean>;
81
+ force: import("zod").ZodOptional<import("zod").ZodBoolean>;
82
+ module: import("zod").ZodOptional<import("zod").ZodString>;
83
+ assignment: import("zod").ZodOptional<import("zod").ZodString>;
84
+ changelog: import("zod").ZodOptional<import("zod").ZodString>;
85
+ stack: import("zod").ZodOptional<import("zod").ZodString>;
86
+ };
87
+ execute(args: {
88
+ manifestPath: string;
89
+ dryRun?: boolean | undefined;
90
+ force?: boolean | undefined;
91
+ module?: string | undefined;
92
+ assignment?: string | undefined;
93
+ changelog?: string | undefined;
94
+ stack?: string | undefined;
95
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
96
+ };
61
97
  export declare const tools: Record<string, unknown>;
62
98
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAOtB,CAAC;AAOH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;CAkB5B,CAAC;AAIH,eAAO,MAAM,cAAc;;;;;;;;CAOzB,CAAC;AAQH,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAIzC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAOtB,CAAC;AAOH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;CAkB5B,CAAC;AAIH,eAAO,MAAM,cAAc;;;;;;;;CAOzB,CAAC;AAIH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAOvB,CAAC;AAIH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;CAOzB,CAAC;AAQH,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAMzC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export declare const publishCourseInputSchema: z.ZodObject<{
3
+ manifestPath: z.ZodString;
4
+ dryRun: z.ZodOptional<z.ZodBoolean>;
5
+ force: z.ZodOptional<z.ZodBoolean>;
6
+ module: z.ZodOptional<z.ZodString>;
7
+ assignment: z.ZodOptional<z.ZodString>;
8
+ changelog: z.ZodOptional<z.ZodString>;
9
+ stack: z.ZodOptional<z.ZodString>;
10
+ }, z.core.$strip>;
11
+ export type PublishCourseInput = z.infer<typeof publishCourseInputSchema>;
12
+ export interface PublishCourseResult {
13
+ published: number;
14
+ created: number;
15
+ unchanged: number;
16
+ skipped: number;
17
+ dryRun: boolean;
18
+ }
19
+ export declare function publishCourseHandler(raw: unknown): Promise<PublishCourseResult>;
20
+ //# sourceMappingURL=publish-course.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-course.d.ts","sourceRoot":"","sources":["../../src/tools/publish-course.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,wBAAwB;;;;;;;;iBAQnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AA6DD,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAoHrF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=publish-course.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-course.test.d.ts","sourceRoot":"","sources":["../../src/tools/publish-course.test.ts"],"names":[],"mappings":""}
@@ -3,7 +3,7 @@ export declare const validateGuideInputSchema: z.ZodObject<{
3
3
  workspace: z.ZodString;
4
4
  }, z.core.$strip>;
5
5
  export type ValidateGuideInput = z.infer<typeof validateGuideInputSchema>;
6
- export type FindingCategory = 'missing_content_dir' | 'invalid_uuid' | 'orphan_order_stem' | 'broken_assessment_ref' | 'taskid_filename_mismatch';
6
+ export type FindingCategory = 'missing_content_dir' | 'invalid_uuid' | 'orphan_order_stem' | 'broken_assessment_ref' | 'taskid_filename_mismatch' | 'double_header' | 'freeze_syntax_error' | 'try_it_syntax_error' | 'missing_referenced_file';
7
7
  export interface Finding {
8
8
  category: FindingCategory;
9
9
  detail: string;
@@ -1 +1 @@
1
- {"version":3,"file":"validate-guide.d.ts","sourceRoot":"","sources":["../../src/tools/validate-guide.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,wBAAwB;;iBAEnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,MAAM,MAAM,eAAe,GACvB,qBAAqB,GACrB,cAAc,GACd,mBAAmB,GACnB,uBAAuB,GACvB,0BAA0B,CAAC;AAE/B,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;CACjB;AAyCD,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,mBAAmB,CAAC,CAwG9B"}
1
+ {"version":3,"file":"validate-guide.d.ts","sourceRoot":"","sources":["../../src/tools/validate-guide.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,wBAAwB;;iBAEnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,MAAM,MAAM,eAAe,GACvB,qBAAqB,GACrB,cAAc,GACd,mBAAmB,GACnB,uBAAuB,GACvB,0BAA0B,GAC1B,eAAe,GACf,qBAAqB,GACrB,qBAAqB,GACrB,yBAAyB,CAAC;AAE9B,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;CACjB;AAgGD,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,mBAAmB,CAAC,CA+K9B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codio-ai/opencode-authoring-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Opencode plugin for authoring Codio guide assignments — orchestrator, subagents, tools, and skills.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,16 +11,10 @@
11
11
  "types": "./dist/index.d.ts"
12
12
  }
13
13
  },
14
- "files": [
15
- "dist",
16
- "src/skills",
17
- "bin",
18
- "README.md",
19
- "LICENSE"
20
- ],
14
+ "files": ["dist", "src/skills", "bin", "README.md", "LICENSE"],
21
15
  "bin": {
22
- "codio-authoring-register": "bin/codio-authoring-register.js",
23
- "codio-authoring-unregister": "bin/codio-authoring-unregister.js"
16
+ "codio-authoring-register": "./bin/codio-authoring-register.js",
17
+ "codio-authoring-unregister": "./bin/codio-authoring-unregister.js"
24
18
  },
25
19
  "scripts": {
26
20
  "build": "bun build src/index.ts --outdir dist --target node --format esm --external @opencode-ai/plugin --external @opencode-ai/sdk --external zod && tsc --emitDeclarationOnly",
@@ -33,17 +27,11 @@
33
27
  "check": "biome check --write .",
34
28
  "check:ci": "biome check ."
35
29
  },
36
- "keywords": [
37
- "opencode",
38
- "opencode-plugin",
39
- "codio",
40
- "assignment-authoring",
41
- "education"
42
- ],
30
+ "keywords": ["opencode", "opencode-plugin", "codio", "assignment-authoring", "education"],
43
31
  "license": "MIT",
44
32
  "repository": {
45
33
  "type": "git",
46
- "url": "git+https://github.com/codio-extensions/codio-authoring-opencode-plugin.git"
34
+ "url": "https://github.com/codio-extensions/codio-authoring-opencode-plugin.git"
47
35
  },
48
36
  "homepage": "https://github.com/codio-extensions/codio-authoring-opencode-plugin#readme",
49
37
  "bugs": "https://github.com/codio-extensions/codio-authoring-opencode-plugin/issues",
@@ -61,6 +49,8 @@
61
49
  "@biomejs/biome": "2.4.11",
62
50
  "@types/node": "^24.6.1",
63
51
  "bun-types": "1.3.12",
52
+ "codio-api-js": "^0.18.0",
53
+ "json-2-csv": "^5.5.10",
64
54
  "typescript": "^5.9.3",
65
55
  "zod": "~4.1.0"
66
56
  },
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: reference-codio-api
3
+ description: Reference for the Codio platform API tools — fetch_course and publish_course. Course manifest format, output structure, credential requirements, and troubleshooting. Use when working with fetched courses or publishing assignments.
4
+ ---
5
+
6
+ # Codio API Tools Reference
7
+
8
+ ## Credentials
9
+
10
+ Both tools read credentials from environment variables. Set these on the Codio box before using either tool:
11
+
12
+ | Variable | Required | Description |
13
+ |---|---|---|
14
+ | `CODIO_CLIENT_ID` | Yes | Codio API client ID |
15
+ | `CODIO_CLIENT_SECRET` | Yes | Codio API client secret |
16
+ | `CODIO_DOMAIN` | No | Codio domain, default `codio.com` |
17
+
18
+ ---
19
+
20
+ ## `fetch_course` — Pull a course from Codio
21
+
22
+ Authenticates with the Codio API, downloads the course export, and writes an AI-ready project to the workspace.
23
+
24
+ ### Inputs
25
+
26
+ | Field | Required | Description |
27
+ |---|---|---|
28
+ | `workspace` | Yes | Absolute path to the Codio workspace root |
29
+ | `courseId` | Yes | Codio course ID (32-char hex string) |
30
+ | `outputDir` | No | Output folder name, default `course-project` |
31
+ | `domain` | No | Codio domain, default `codio.com` |
32
+
33
+ ### Output
34
+
35
+ Returns `{ outputDir, manifestPath, assignmentsCsvPath, moduleCount, assignmentCount }`.
36
+
37
+ Writes to `<outputDir>/` inside the workspace:
38
+
39
+ ```
40
+ <outputDir>/
41
+ ├── course-manifest.json ← machine-readable index; source of truth for publish_course
42
+ ├── course-assignments.csv ← flat CSV for review
43
+ └── course-content/
44
+ └── <module-folder>/
45
+ └── <assignment-folder>/
46
+ └── .guides/ ← guide pages, assessments, images
47
+ ```
48
+
49
+ ---
50
+
51
+ ## `publish_course` — Push assignments back to Codio
52
+
53
+ Reads `course-manifest.json` from the project folder and publishes each assignment to Codio.
54
+
55
+ ### Inputs
56
+
57
+ | Field | Required | Description |
58
+ |---|---|---|
59
+ | `manifestPath` | Yes | Absolute path to `course-manifest.json` |
60
+ | `dryRun` | No | Preview actions without making changes |
61
+ | `force` | No | Publish all, bypassing change detection |
62
+ | `module` | No | Only process this module (name or folder) |
63
+ | `assignment` | No | Only process this assignment (name or folder) |
64
+ | `changelog` | No | Changelog text applied to all publishes |
65
+ | `stack` | No | Force a specific stack ID for all assignments |
66
+
67
+ ### Output
68
+
69
+ Returns `{ published, created, unchanged, skipped }`.
70
+
71
+ Writes back any new IDs to `course-manifest.json` immediately after each resource is created.
72
+
73
+ ### Change detection
74
+
75
+ The tool skips existing assignments whose content hash matches the stored `contentHash` in the manifest. Pass `force: true` to bypass. New assignments (no `id`) are always published.
76
+
77
+ ### Stacks
78
+
79
+ - **New assignments** always use the default stack.
80
+ - **Existing assignments** preserve their current stack unless `stack` is supplied.
81
+
82
+ ---
83
+
84
+ ## course-manifest.json format
85
+
86
+ Created by `fetch_course`, consumed by `publish_course`. Both tools write back IDs as resources are created/resolved.
87
+
88
+ ```jsonc
89
+ {
90
+ "schemaVersion": 2,
91
+ "courseId": "c87b4540de108faad55c8cefe7847492",
92
+ "exportedAt": "2026-06-12T10:00:00.000Z",
93
+ "counts": { "modules": 2, "assignments": 6 },
94
+ "paths": {
95
+ "assignmentsCsv": "course-assignments.csv",
96
+ "courseContent": "course-content"
97
+ },
98
+ "modules": [
99
+ {
100
+ "id": "...", // Resolved or created by publish_course; written back
101
+ "name": "Module Name",
102
+ "folder": "module-name",
103
+ "path": "course-content/module-name",
104
+ "assignmentCount": 3,
105
+ "assignments": [
106
+ {
107
+ "id": "...", // Created by publish_course if empty; written back
108
+ "name": "Assignment Name",
109
+ "folder": "assignment-name",
110
+ "path": "course-content/module-name/assignment-name",
111
+ "contentHash": "..." // Written after each successful publish; used for change detection
112
+ }
113
+ ]
114
+ }
115
+ ]
116
+ }
117
+ ```
118
+
119
+ ### Folder slug rules
120
+
121
+ Codio uses lowercase slugs: normalise to ASCII, lowercase, replace each non-alphanumeric character with `-`, trim leading/trailing hyphens. Example: `"1.1 – Intro"` → `"1-1---intro"`.
122
+
123
+ ---
124
+
125
+ ## Typical workflow
126
+
127
+ ```
128
+ fetch_course(courseId)
129
+ → course-project/course-manifest.json + course-content/...
130
+
131
+ [edit assignment pages using authoring tools]
132
+
133
+ publish_course(manifestPath, dryRun: true) ← preview first
134
+ publish_course(manifestPath) ← publish for real
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Troubleshooting
140
+
141
+ | Error | Fix |
142
+ |---|---|
143
+ | `CODIO_CLIENT_ID ... must be set` | Set env vars on the box |
144
+ | `course-manifest.json not found` | Pass the absolute path to the manifest; run from or inside the project folder |
145
+ | `path not found` for an assignment | The folder referenced in the manifest doesn't exist — generate content there first |
146
+ | `Required command not found: unzip` | Install `unzip` via the box package manager |
147
+ | API 401 / auth error | Credentials are invalid or expired |
148
+ | API 429 rate limit | Library retries automatically; wait and re-run if still failing |
149
+ | `no module ID available` | Module creation failed earlier in the run — check the error above it |