recess-cli 1.0.1 → 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/README.md +56 -3
- package/dist/args.js +43 -2
- package/dist/cli.js +1090 -2
- package/dist/setup.js +40 -2
- package/dist/skill-update.js +150 -0
- package/dist/skills-cache.js +0 -0
- package/package.json +9 -2
- package/scripts/postinstall.mjs +44 -0
- package/skill/recess-cli/SKILL.md +204 -14
- package/skill/recess-cli/reference/cancellation-credits.md +1 -1
- package/skill/recess-cli/reference/class-ops-reschedule.md +1 -1
- package/skill/recess-cli/reference/goal-authoring.md +274 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ recess setup
|
|
|
13
13
|
|
|
14
14
|
`setup` installs the bundled skill for both Codex and Claude and then opens Recess SSO in your browser (skip the browser step with `--skill-only`; it is also skipped when a live session already exists). Restart your agent afterwards so it discovers the skill. `npx -y recess-cli setup` works too, but leaves no `recess` on your PATH — which is the command the installed skill tells the agent to run — so `setup` warns when it detects it is running from an npx cache.
|
|
15
15
|
|
|
16
|
-
Publishing
|
|
16
|
+
Publishing rides the production deploy (`.github/workflows/admin-cli-publish.yml`): bump `version` in `apps/admin-cli/package.json` in a normal PR to `staging`, and it publishes when `staging` promotes to `production`. A production deploy that did not bump the version is a no-op — a `gate` job checks the version against npm first. The same workflow is still dispatchable by hand for out-of-band releases. pnpm packs the CLI so the workspace `catalog:` dependency becomes a real range; npm publishes that tarball through the `admin-cli-publish.yml` OIDC trusted publisher configured on npmjs.com.
|
|
17
17
|
|
|
18
18
|
## Install (from a checkout — CLI development)
|
|
19
19
|
|
|
@@ -25,7 +25,7 @@ pnpm --dir apps/admin-cli run client:generate
|
|
|
25
25
|
pnpm --dir apps/admin-cli run install-persistent
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
`install-persistent` copies a self-contained build (non-test `dist/` JS + the `openapi-fetch` runtime dep) to `~/.recess-cli/cli/` and points `~/.local/bin/recess` at it — the install keeps working after the checkout or worktree it was built from is deleted. Use it on any machine that operates on production. `install-local` instead symlinks `~/.local/bin/recess` straight to this checkout's `dist/index.js` so rebuilds are picked up live — use it only while actively developing the CLI, and expect the link to die with the worktree. Both targets install the bundled skill for Codex at `${CODEX_HOME:-~/.codex}/skills/recess-cli` and Claude at `${CLAUDE_CONFIG_DIR:-~/.claude}/skills/recess-cli`. The skill is a multi-file bundle: `skill/recess-cli/SKILL.md` carries the safety model, auth troubleshooting, JSON contract, and command quick reference, and routes to the deep workflow playbooks in `skill/recess-cli/reference/` (billing, MAP scores, payouts, class ops).
|
|
28
|
+
`install-persistent` copies a self-contained build (non-test `dist/` JS + the `openapi-fetch` runtime dep) to `~/.recess-cli/cli/` and points `~/.local/bin/recess` at it — the install keeps working after the checkout or worktree it was built from is deleted. Use it on any machine that operates on production. `install-local` instead symlinks `~/.local/bin/recess` straight to this checkout's `dist/index.js` so rebuilds are picked up live — use it only while actively developing the CLI, and expect the link to die with the worktree. Both targets install the bundled skill for Codex at `${CODEX_HOME:-~/.codex}/skills/recess-cli` and Claude at `${CLAUDE_CONFIG_DIR:-~/.claude}/skills/recess-cli`. The skill is a multi-file bundle: `skill/recess-cli/SKILL.md` carries the safety model, auth troubleshooting, JSON contract, and command quick reference, and routes to the deep workflow playbooks in `skill/recess-cli/reference/` (billing, MAP scores, payouts, class ops, onboarding, and goal authoring).
|
|
29
29
|
|
|
30
30
|
## One-time SSO setup
|
|
31
31
|
|
|
@@ -92,4 +92,57 @@ human explicitly approves the exceptional `--allow-strand` override.
|
|
|
92
92
|
|
|
93
93
|
MAP uploads accept one PDF up to 15 MB. The preview includes the resolved path, byte count, and SHA-256 without contacting the API; the confirmed command sends the report to the existing tutor-dashboard extraction route.
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
## Authoring learning content
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
recess --json skills get os-v2-goal-template-builder --all-references
|
|
99
|
+
recess --json goal-templates validate-spec --file ./template.json # iterate; writes nothing
|
|
100
|
+
recess --json goal-templates create --file ./template.json # preview, exit 2
|
|
101
|
+
recess --json goal-templates create --file ./template.json --confirm
|
|
102
|
+
recess --json goal-templates patch-spec <id-or-slug> --expected-version 7 --patches-file ./patches.json
|
|
103
|
+
recess --json mesa files write --student <kid-id> --draft <draft-slug> --source-dir ./workspace
|
|
104
|
+
recess --json goal-templates capture-snapshot <id-or-slug> --source-draft <draft-slug> --student <kid-id> --dry-run
|
|
105
|
+
recess --json goal-templates apply <id-or-slug> --answers-file ./answers.json --dry-run
|
|
106
|
+
recess --json goals create --student <kid-id> --title "..." --description-file ./goal.md
|
|
107
|
+
recess --json mesa files list --student <kid-id> --goal <goal-id>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Keeping the agent skill current
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
recess --json --version # {cliVersion, skillVersion}
|
|
114
|
+
recess --json doctor # .skill reports whether a newer bundle exists
|
|
115
|
+
recess --json setup --skill-only # bundled copy, then the served upgrade
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The CLI's own agent skill is **both** bundled in this package and served from
|
|
119
|
+
`GET /admin/cli-skill/`. The bundled copy is the floor — it works offline, before a session exists,
|
|
120
|
+
and always matches the installed binary; `postinstall` refreshes it on every `npm install -g`. The
|
|
121
|
+
served copy is the upgrade: wording and Gotcha edits reach installed CLIs on the next deploy instead
|
|
122
|
+
of the next npm release, fenced by the bundle's `minCliVersion` so an older binary keeps its bundled
|
|
123
|
+
copy rather than reading a skill written for a newer one. An unreachable server is never an error.
|
|
124
|
+
|
|
125
|
+
`skills` (plural, a different thing) serves the in-product tutor skills — the same documents the `recess.gg/ai` agent loads —
|
|
126
|
+
read-only over your admin session from the **private** `packages/skills` submodule. They are
|
|
127
|
+
deliberately **not** bundled into this package, which is published publicly on npm: one source of
|
|
128
|
+
truth, and a skills-repo update reaches CLI agents with no CLI release. Load
|
|
129
|
+
`os-v2-goal-template-builder` before authoring; a spec that passes `validate-spec` is structurally
|
|
130
|
+
valid, not good. Responses cache under `~/.recess-cli/skills-cache/` (`--refresh` re-fetches).
|
|
131
|
+
|
|
132
|
+
Every template created here is `setupMode: DETERMINISTIC_WORKFLOW` and **cannot be converted back**,
|
|
133
|
+
so the confirmation gate is load-bearing. `create` runs a real server-side validation before the
|
|
134
|
+
gate, so `preview.details` carries the handler, goal shape, wizard step keys, and spec inventory the
|
|
135
|
+
*server* resolved rather than a client-side guess; `apply` runs the backend's own `dryRun` and
|
|
136
|
+
previews the per-student outcome. `set-metadata` and `delete` require `--expected-version` from
|
|
137
|
+
`get`; a stale value 409s `STALE_WRITE` without writing. `set-metadata` cannot send a
|
|
138
|
+
`setupWorkflowSpec` at all. Use `patch-spec` with a JSON array of bounded JSON-Pointer operations;
|
|
139
|
+
it always runs the backend's guarded preview first and requires the preview's exact loss token in
|
|
140
|
+
addition to `--confirm` when protected template data would be removed.
|
|
141
|
+
|
|
142
|
+
MODULE_BACKED BLUEPRINT content is authored as a local workspace tree, batch-upserted to a named
|
|
143
|
+
Mesa draft, then attached with `capture-snapshot`. Both mutations run server previews before the
|
|
144
|
+
confirmation gate. Mesa upserts are compare-and-set against the previewed repo change; capture is
|
|
145
|
+
fenced to both that source change and the template version. Direct live-goal `modules/` and
|
|
146
|
+
`state/` writes are blocked because those files have database projections.
|
|
147
|
+
|
|
148
|
+
See `recess --help` for the complete command surface. The raw escape hatch is intentionally read-only: `recess --json request get /path`.
|
package/dist/args.js
CHANGED
|
@@ -1,23 +1,45 @@
|
|
|
1
1
|
import { CliError } from "./errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Flags that may legitimately appear more than once. Reading one through
|
|
4
|
+
* `flagString` still yields the LAST value, which is why every consumer of a
|
|
5
|
+
* repeatable flag must use `flagList` instead.
|
|
6
|
+
*/
|
|
7
|
+
export const REPEATABLE_FLAGS = new Set(["kid", "unassign"]);
|
|
2
8
|
const BOOLEAN_FLAGS = new Set([
|
|
9
|
+
"all-references",
|
|
3
10
|
"allow-strand",
|
|
4
11
|
"cancel-subscriptions",
|
|
5
12
|
"archived",
|
|
6
13
|
"confirm",
|
|
14
|
+
"dry-run",
|
|
7
15
|
"full",
|
|
8
16
|
"help",
|
|
9
17
|
"immediate",
|
|
18
|
+
"include-deleted",
|
|
10
19
|
"mirrored",
|
|
11
20
|
"no-collision",
|
|
12
21
|
"json",
|
|
22
|
+
"refresh",
|
|
13
23
|
"restore",
|
|
14
24
|
"revoke",
|
|
15
25
|
"send-email",
|
|
26
|
+
"spec-only",
|
|
27
|
+
"starter-only",
|
|
16
28
|
"visual-only",
|
|
17
29
|
]);
|
|
18
30
|
export function parseArgs(args) {
|
|
19
31
|
const positionals = [];
|
|
20
32
|
const flags = new Map();
|
|
33
|
+
const repeated = new Map();
|
|
34
|
+
const record = (name, value) => {
|
|
35
|
+
if (!REPEATABLE_FLAGS.has(name))
|
|
36
|
+
return;
|
|
37
|
+
const existing = repeated.get(name);
|
|
38
|
+
if (existing)
|
|
39
|
+
existing.push(value);
|
|
40
|
+
else
|
|
41
|
+
repeated.set(name, [value]);
|
|
42
|
+
};
|
|
21
43
|
for (let index = 0; index < args.length; index += 1) {
|
|
22
44
|
const value = args[index];
|
|
23
45
|
if (!value.startsWith("--")) {
|
|
@@ -26,7 +48,10 @@ export function parseArgs(args) {
|
|
|
26
48
|
}
|
|
27
49
|
const equalsAt = value.indexOf("=");
|
|
28
50
|
if (equalsAt > 2) {
|
|
29
|
-
|
|
51
|
+
const name = value.slice(2, equalsAt);
|
|
52
|
+
const flagValue = value.slice(equalsAt + 1);
|
|
53
|
+
flags.set(name, flagValue);
|
|
54
|
+
record(name, flagValue);
|
|
30
55
|
continue;
|
|
31
56
|
}
|
|
32
57
|
const name = value.slice(2);
|
|
@@ -37,13 +62,29 @@ export function parseArgs(args) {
|
|
|
37
62
|
const next = args[index + 1];
|
|
38
63
|
if (next && !next.startsWith("--")) {
|
|
39
64
|
flags.set(name, next);
|
|
65
|
+
record(name, next);
|
|
40
66
|
index += 1;
|
|
41
67
|
}
|
|
42
68
|
else {
|
|
43
69
|
flags.set(name, true);
|
|
44
70
|
}
|
|
45
71
|
}
|
|
46
|
-
return { positionals, flags };
|
|
72
|
+
return { positionals, flags, repeated };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Every value given for a repeatable flag, in order.
|
|
76
|
+
*
|
|
77
|
+
* Returns `[]` when the flag is absent, so a caller distinguishes "none given"
|
|
78
|
+
* from "given empty" by checking length rather than by a null dance.
|
|
79
|
+
*/
|
|
80
|
+
export function flagList(parsed, name) {
|
|
81
|
+
if (!REPEATABLE_FLAGS.has(name)) {
|
|
82
|
+
// A wiring defect, not a user error: reading a non-repeatable flag as a
|
|
83
|
+
// list would silently return [] however many times it was passed.
|
|
84
|
+
throw new CliError("invalid_arguments", `--${name} is not declared repeatable; add it to REPEATABLE_FLAGS.`);
|
|
85
|
+
}
|
|
86
|
+
const values = parsed.repeated.get(name) ?? [];
|
|
87
|
+
return values.map((value) => value.trim()).filter(Boolean);
|
|
47
88
|
}
|
|
48
89
|
export function flagString(parsed, name, options = {}) {
|
|
49
90
|
const value = parsed.flags.get(name);
|