cotutor 0.4.4 → 0.4.5
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/CHANGELOG.md +9 -1
- package/INSTALL.md +159 -0
- package/README.md +35 -19
- package/dist/app/cli/activities.d.ts +1 -1
- package/dist/app/cli/activities.js +2 -2
- package/dist/app/cli/cert.js +2 -1
- package/dist/app/cli/chalk/main.d.ts +0 -1
- package/dist/app/cli/chalk/main.js +8 -12
- package/dist/app/cli/commands.d.ts +50 -0
- package/dist/app/cli/commands.js +353 -0
- package/dist/app/cli/doctor.js +32 -17
- package/dist/app/cli/guide.d.ts +6 -0
- package/dist/app/cli/guide.js +17 -0
- package/dist/app/cli/init.d.ts +2 -0
- package/dist/app/cli/init.js +60 -6
- package/dist/app/cli/main.js +109 -46
- package/dist/app/cli/migrate.js +1 -1
- package/dist/app/cli/port.js +2 -10
- package/dist/app/cli/sandbox.js +2 -7
- package/dist/app/cli/service.d.ts +4 -1
- package/dist/app/cli/service.js +5 -2
- package/dist/app/cli/shipped.js +1 -1
- package/dist/app/cli/skeleton.d.ts +2 -0
- package/dist/app/cli/skeleton.js +3 -2
- package/dist/app/cli/skills.js +2 -2
- package/dist/app/cli/themes.js +2 -2
- package/dist/app/cli/tray.d.ts +6 -1
- package/dist/app/cli/tray.js +8 -5
- package/dist/app/cli/tutors.d.ts +2 -2
- package/dist/app/cli/tutors.js +4 -4
- package/dist/app/cli/update-cmd.js +4 -4
- package/dist/app/cli/usage.d.ts +26 -2
- package/dist/app/cli/usage.js +167 -38
- package/dist/app/http/app.js +1 -1
- package/dist/app/skills/analyze-doc.js +4 -8
- package/dist/app/skills/home-doc.js +3 -7
- package/dist/app/skills/tune-doc.js +4 -8
- package/dist/app/workspace.d.ts +16 -3
- package/dist/app/workspace.js +31 -9
- package/dist/runtime/home/home.js +1 -1
- package/dist/runtime/runner.js +2 -2
- package/dist/shared/home.d.ts +2 -0
- package/dist/shared/home.js +9 -0
- package/dist/ui/dev/dev-page.js +5 -5
- package/dist/ui/parent/parent-home-page.js +1 -1
- package/install.ps1 +5 -4
- package/install.sh +3 -3
- package/package.json +7 -5
- package/skills/cotutor-analyze/SKILL.md +1 -1
- package/skills/cotutor-analyze/references//345/221/275/344/273/244/344/270/216/346/226/207/344/273/266.md +168 -10
- package/skills/cotutor-home/SKILL.md +1 -1
- package/skills/cotutor-home/references//345/221/275/344/273/244.md +75 -7
- package/skills/cotutor-tune/SKILL.md +8 -8
- package/skills/cotutor-tune/references//345/255/227/346/256/265.md +164 -10
- package/skills/cotutor-tutor/SKILL.md +1 -1
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* tests/skills.test.ts 断言入库的和生成的一致——改老师团时能改哪些字段、缺省是什么,永远和契约同源。SKILL.md 本体(三条路子、禁区)是手写的。
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import {
|
|
7
|
+
import { commandsReference } from "../cli/usage.js";
|
|
8
8
|
import { POLICY_DEFAULTS, PolicySchema, TutorSchema } from "../../schema/config.js";
|
|
9
9
|
import { AGENT_FRONTMATTER_KEYS } from "../../runtime/agent-file.js";
|
|
10
10
|
export const TUNE_SKILL = 'cotutor-tune';
|
|
11
|
-
const TUNE_COMMANDS = ['add', 'add-theme', '
|
|
11
|
+
const TUNE_COMMANDS = ['add', 'add-theme', 'refresh', 'reset', 'doctor', 'pack', 'replay', 'compare', 'show'];
|
|
12
12
|
function unwrap(s) {
|
|
13
13
|
let cur = s;
|
|
14
14
|
for (let i = 0; i < 5; i++) {
|
|
@@ -39,10 +39,6 @@ function rows(shape, defaults, prefix = '') {
|
|
|
39
39
|
return out;
|
|
40
40
|
}
|
|
41
41
|
export function tuneReferenceDoc() {
|
|
42
|
-
const cmds = USAGE.split('\n').filter((l) => {
|
|
43
|
-
const m = /^\s+cotutor (\S+)/.exec(l);
|
|
44
|
-
return m !== null && TUNE_COMMANDS.includes(m[1]);
|
|
45
|
-
});
|
|
46
42
|
// tutors 字段的缺省从 zod 的 .default() 取(enabled / hidden);display 是必需的,给个占位再抹掉
|
|
47
43
|
const tutorDefaults = TutorSchema.parse({ display: 'x' });
|
|
48
44
|
delete tutorDefaults.display;
|
|
@@ -68,10 +64,10 @@ ${tutorRows.join('\n')}
|
|
|
68
64
|
|---|---|---|---|
|
|
69
65
|
${policyRows.join('\n')}
|
|
70
66
|
|
|
71
|
-
## 相关命令(
|
|
67
|
+
## 相关命令(每条就是 cotutor <命令> --help 的内容)
|
|
72
68
|
|
|
73
69
|
\`\`\`
|
|
74
|
-
${
|
|
70
|
+
${commandsReference(TUNE_COMMANDS)}
|
|
75
71
|
\`\`\`
|
|
76
72
|
`;
|
|
77
73
|
}
|
package/dist/app/workspace.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ConfigError, UsageError } from '../shared/errors.ts';
|
|
2
|
-
import { redactDeep, redactHome } from '../shared/home.ts';
|
|
2
|
+
import { expandPath, redactDeep, redactHome } from '../shared/home.ts';
|
|
3
3
|
import { type RootSource, type SandboxMark, type Workspace } from '../schema/workspace.ts';
|
|
4
4
|
import { type CotutorConfig } from '../schema/index.ts';
|
|
5
|
-
export { ConfigError, UsageError, redactDeep, redactHome };
|
|
5
|
+
export { ConfigError, UsageError, expandPath, redactDeep, redactHome };
|
|
6
6
|
export type { RootSource, Workspace };
|
|
7
7
|
export declare const CONFIG_FILE = "cotutor.json";
|
|
8
8
|
export declare const USER_CONFIG: string;
|
|
@@ -13,13 +13,26 @@ export interface ResolvedRoot {
|
|
|
13
13
|
root: string;
|
|
14
14
|
source: RootSource;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
/** p 是 dir 本身或在它里面 */
|
|
17
|
+
export declare function isInside(p: string, dir: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* vault 与 workspace 分开:谁也不在谁里面(2026-11-09)。vault 是家长的长期记忆,workspace 是机器的运行记录;
|
|
20
|
+
* 混在一起时 Obsidian 里满是 agents/、conversations/,扫 vault 要走遍对话与照片,备份恢复把家长的笔记跟着 workspace 一起盖掉。
|
|
21
|
+
* 旧版 init 不写 paths.vault(缺省 `.`,vault 就是 workspace 根),那样的照旧读得进来,doctor 的 vault.separate 提一句。
|
|
22
|
+
*/
|
|
23
|
+
export declare const vaultSeparate: (root: string, vault: string) => boolean;
|
|
24
|
+
/** init 缺省的 vault:workspace 旁边的 <目录名>-vault(~/cotutor/ming → ~/cotutor/ming-vault;不在「文稿」与 iCloud 里,后台服务读得到) */
|
|
25
|
+
export declare const defaultVaultOf: (root: string) => string;
|
|
26
|
+
/** 写进 cotutor.json 的路径:家目录下的写成 ~/…(输出里不带用户名,换了用户名也对得上),别处写绝对路径 */
|
|
27
|
+
export declare function homePath(abs: string): string;
|
|
17
28
|
/** 读 JSON:不存在 → null;坏 → ConfigError */
|
|
18
29
|
export declare function readJson(file: string): unknown | null;
|
|
19
30
|
/** 从 dir 向上找 cotutor.json,找到返回目录 */
|
|
20
31
|
export declare function findUp(dir: string): string | null;
|
|
21
32
|
/** ~/cotutor/ 下带 cotutor.json 的子目录 */
|
|
22
33
|
export declare function homeWorkspaces(homeRoot?: string): string[];
|
|
34
|
+
/** 这台电脑上认得的孩子:~/cotutor/ 下的,加用户配置指的那个(在别处 --dir 建的) */
|
|
35
|
+
export declare function knownWorkspaces(): string[];
|
|
23
36
|
export interface ResolveOptions {
|
|
24
37
|
env?: NodeJS.ProcessEnv;
|
|
25
38
|
cwd?: string;
|
package/dist/app/workspace.js
CHANGED
|
@@ -9,24 +9,35 @@
|
|
|
9
9
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
10
10
|
import { readFile, writeFile, rename } from 'node:fs/promises';
|
|
11
11
|
import { ConfigError, UsageError } from "../shared/errors.js";
|
|
12
|
-
import { redactDeep, redactHome } from "../shared/home.js";
|
|
12
|
+
import { expandPath, redactDeep, redactHome } from "../shared/home.js";
|
|
13
13
|
import { SANDBOX_MARK, SKILLS_DIR, SandboxMarkSchema } from "../schema/workspace.js";
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
|
-
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
15
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
16
16
|
import { CotutorConfigSchema, PATH_ROLES, ROLE_DEFAULTS, WORKSPACE_ROLES, explainIssues, } from "../schema/index.js";
|
|
17
17
|
// 挪走了的几样照旧从这里转出(旧的引法不用改)
|
|
18
|
-
export { ConfigError, UsageError, redactDeep, redactHome };
|
|
18
|
+
export { ConfigError, UsageError, expandPath, redactDeep, redactHome };
|
|
19
19
|
export const CONFIG_FILE = 'cotutor.json';
|
|
20
20
|
export const USER_CONFIG = join(homedir(), '.config', 'cotutor', 'config.json');
|
|
21
21
|
/** 机器级自签证书目录(cotutor cert 写 cert.pem + key.pem):证书签的是本机名与局域网 IP,与孩子无关,所以不在 workspace 里 */
|
|
22
22
|
export const USER_CERT_DIR = join(homedir(), '.config', 'cotutor', 'certs');
|
|
23
23
|
export const HOME_ROOT = join(homedir(), 'cotutor');
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
/** p 是 dir 本身或在它里面 */
|
|
25
|
+
export function isInside(p, dir) {
|
|
26
|
+
const r = relative(dir, p);
|
|
27
|
+
return r === '' || (r !== '..' && !r.startsWith(`..${sep}`) && !isAbsolute(r));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* vault 与 workspace 分开:谁也不在谁里面(2026-11-09)。vault 是家长的长期记忆,workspace 是机器的运行记录;
|
|
31
|
+
* 混在一起时 Obsidian 里满是 agents/、conversations/,扫 vault 要走遍对话与照片,备份恢复把家长的笔记跟着 workspace 一起盖掉。
|
|
32
|
+
* 旧版 init 不写 paths.vault(缺省 `.`,vault 就是 workspace 根),那样的照旧读得进来,doctor 的 vault.separate 提一句。
|
|
33
|
+
*/
|
|
34
|
+
export const vaultSeparate = (root, vault) => !isInside(vault, root) && !isInside(root, vault);
|
|
35
|
+
/** init 缺省的 vault:workspace 旁边的 <目录名>-vault(~/cotutor/ming → ~/cotutor/ming-vault;不在「文稿」与 iCloud 里,后台服务读得到) */
|
|
36
|
+
export const defaultVaultOf = (root) => join(dirname(root), `${basename(root)}-vault`);
|
|
37
|
+
/** 写进 cotutor.json 的路径:家目录下的写成 ~/…(输出里不带用户名,换了用户名也对得上),别处写绝对路径 */
|
|
38
|
+
export function homePath(abs) {
|
|
39
|
+
const rel = relative(homedir(), abs);
|
|
40
|
+
return rel && !rel.startsWith('..') && !isAbsolute(rel) ? `~/${rel.split(sep).join('/')}` : abs;
|
|
30
41
|
}
|
|
31
42
|
const isDir = (p) => {
|
|
32
43
|
try {
|
|
@@ -84,6 +95,17 @@ export function homeWorkspaces(homeRoot = HOME_ROOT) {
|
|
|
84
95
|
return [];
|
|
85
96
|
}
|
|
86
97
|
}
|
|
98
|
+
/** 这台电脑上认得的孩子:~/cotutor/ 下的,加用户配置指的那个(在别处 --dir 建的) */
|
|
99
|
+
export function knownWorkspaces() {
|
|
100
|
+
const roots = new Set(homeWorkspaces());
|
|
101
|
+
try {
|
|
102
|
+
const user = readJson(USER_CONFIG);
|
|
103
|
+
if (typeof user?.workspace === 'string' && user.workspace)
|
|
104
|
+
roots.add(expandPath(user.workspace, homedir()));
|
|
105
|
+
}
|
|
106
|
+
catch { /* 用户配置坏了 doctor 去报 */ }
|
|
107
|
+
return [...roots];
|
|
108
|
+
}
|
|
87
109
|
export function resolveRoot(override, opts = {}) {
|
|
88
110
|
const env = opts.env ?? process.env;
|
|
89
111
|
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
@@ -127,7 +127,7 @@ export function homeIssues(doc, ctx) {
|
|
|
127
127
|
if (act && ctx.activities) {
|
|
128
128
|
const got = ctx.activities.get(`${name}/${act.name}`);
|
|
129
129
|
if (!got)
|
|
130
|
-
out.push({ level: 'fix', line, card: n, button: k, text: b.kind === 'lecture' && !b.activity ? `${t.display}的「${b.label}」:看完怎么聊写在 agents/${name}/activities/${LECTURE_ACTIVITY}.md,这份不在(cotutor
|
|
130
|
+
out.push({ level: 'fix', line, card: n, button: k, text: b.kind === 'lecture' && !b.activity ? `${t.display}的「${b.label}」:看完怎么聊写在 agents/${name}/activities/${LECTURE_ACTIVITY}.md,这份不在(cotutor refresh 补出厂的;或按钮下面写一行 activity: <名> 换一个)` : `${t.display}的「${b.label}」:agents/${name}/activities/${act.name}.md 不在(cotutor activity check ${name} 看这位老师有哪些)` });
|
|
131
131
|
else {
|
|
132
132
|
if (got.fixes)
|
|
133
133
|
out.push({ level: 'fix', line, card: n, button: k, text: `${t.display}的「${b.label}」:activity 文件 ${act.name}.md 有 ${got.fixes} 处要改(cotutor activity check ${name} ${act.name})` });
|
package/dist/runtime/runner.js
CHANGED
|
@@ -92,7 +92,7 @@ export async function gatherContext(ws, tutor, input, report) {
|
|
|
92
92
|
pack.rules = TUTOR_RULES_PATH;
|
|
93
93
|
}
|
|
94
94
|
catch {
|
|
95
|
-
pack.rules = `缺:${TUTOR_RULES_PATH} 不在(cotutor
|
|
95
|
+
pack.rules = `缺:${TUTOR_RULES_PATH} 不在(cotutor refresh 补),照你老师文件里的做`;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
pack.notes.push(...mem.notes);
|
|
@@ -186,7 +186,7 @@ async function systemParts(ws, tutor, policy, pack, act = null) {
|
|
|
186
186
|
return { systemBody: agentBody, activityInSystem: false };
|
|
187
187
|
const guide = await boardGuideOf(ws, tutor);
|
|
188
188
|
if (!guide) {
|
|
189
|
-
pack.boardGuide = `缺:${BOARD_GUIDE_PATH} 不在(cotutor
|
|
189
|
+
pack.boardGuide = `缺:${BOARD_GUIDE_PATH} 不在(cotutor refresh 补),照你会的写`;
|
|
190
190
|
return { systemBody: agentBody, activityInSystem: false };
|
|
191
191
|
}
|
|
192
192
|
pack.boardGuide = BOARD_GUIDE_IN_SYSTEM;
|
package/dist/shared/home.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
/** cotutor.json 里的路径:~ 与 ~/ 是家目录,绝对的照用,相对的相对 base(从 app/workspace.ts 挪来,2026-11-09:托盘不带 zod 也要用) */
|
|
2
|
+
export declare function expandPath(p: string, base: string): string;
|
|
1
3
|
export declare function redactHome(value: string): string;
|
|
2
4
|
export declare function redactDeep<T>(value: T): T;
|
package/dist/shared/home.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/** 给人看的路径里把家目录换成 $HOME(报错、doctor、--json 输出不带出用户名;从 app/workspace.ts 挪来,2026-10-29) */
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
/** cotutor.json 里的路径:~ 与 ~/ 是家目录,绝对的照用,相对的相对 base(从 app/workspace.ts 挪来,2026-11-09:托盘不带 zod 也要用) */
|
|
5
|
+
export function expandPath(p, base) {
|
|
6
|
+
if (p === '~')
|
|
7
|
+
return homedir();
|
|
8
|
+
if (p.startsWith('~/'))
|
|
9
|
+
return join(homedir(), p.slice(2));
|
|
10
|
+
return isAbsolute(p) ? p : resolve(base, p);
|
|
11
|
+
}
|
|
3
12
|
export function redactHome(value) {
|
|
4
13
|
const home = homedir();
|
|
5
14
|
return home && value.startsWith(home) ? '$HOME' + value.slice(home.length) : value;
|
package/dist/ui/dev/dev-page.js
CHANGED
|
@@ -1297,7 +1297,7 @@ ${pageDomJs(4000)}
|
|
|
1297
1297
|
catch (e) { feedback(card, false, e.message); }
|
|
1298
1298
|
} } }, '补上'))));
|
|
1299
1299
|
card.append(h('ul', { class: 'gaps' }, ...gaps.map((g) => h('li', {}, h('span', { class: g.kind === 'retired' ? 'plus minus' : 'plus' }, g.kind === 'retired' ? '−' : '+'), h('span', {}, g.detail)))));
|
|
1300
|
-
card.append(h('p', { class: 'hint' }, '新版 cotutor 带来的新老师、新运行时、命令模板里的新旗标(+),和新版不再读的出厂段(−)。cotutor.json 是你的政策文件,机器不会自己改它,所以要你点一下。只改上面这些,你改过的值一个都不动;终端里等同于 cotutor
|
|
1300
|
+
card.append(h('p', { class: 'hint' }, '新版 cotutor 带来的新老师、新运行时、命令模板里的新旗标(+),和新版不再读的出厂段(−)。cotutor.json 是你的政策文件,机器不会自己改它,所以要你点一下。只改上面这些,你改过的值一个都不动;终端里等同于 cotutor refresh --config。'));
|
|
1301
1301
|
return card;
|
|
1302
1302
|
};
|
|
1303
1303
|
|
|
@@ -1590,7 +1590,7 @@ ${pageDomJs(4000)}
|
|
|
1590
1590
|
const rows = listRows();
|
|
1591
1591
|
const first = (want && rows.find((r) => (isTutorFile(r) ? r.tutor : r.tutor + '/' + r.name) === want)) || rows[0];
|
|
1592
1592
|
if (first) return openActivity(first.tutor, first.name, true, first.kind);
|
|
1593
|
-
$('#activity').replaceChildren(h('div', { class: 'wrap act-wrap' }, actHead(), h('p', { class: 'note' }, '这个 workspace 里还没有 activity(agents/<老师>/activities/<名>.md);cotutor
|
|
1593
|
+
$('#activity').replaceChildren(h('div', { class: 'wrap act-wrap' }, actHead(), h('p', { class: 'note' }, '这个 workspace 里还没有 activity(agents/<老师>/activities/<名>.md);cotutor refresh 会补上出厂的。')));
|
|
1594
1594
|
};
|
|
1595
1595
|
|
|
1596
1596
|
const openActivity = async (t, n, force, kind) => {
|
|
@@ -1875,7 +1875,7 @@ ${pageDomJs(4000)}
|
|
|
1875
1875
|
if (!r.diff) return [h('p', { class: 'note' }, tf ? '自家加的老师,没有出厂那份可比。' : '自家加的 activity,没有出厂那份可比。')];
|
|
1876
1876
|
const out = [h('div', { class: 'srow' }, r.diff.length ? h('span', { class: 'st accent' }, '和出厂不一样 ' + r.diff.length + ' 处') : h('span', { class: 'st ok' }, '和出厂一样'), h('span', { class: 'note' }, '含没存的')),
|
|
1877
1877
|
...r.diff.map((d) => hunkEl(d, true))];
|
|
1878
|
-
out.push(h('p', { class: 'note' }, c.state === 'upgradable' ? '这份没改过,是旧版出厂的;cotutor
|
|
1878
|
+
out.push(h('p', { class: 'note' }, c.state === 'upgradable' ? '这份没改过,是旧版出厂的;cotutor refresh 会换成新的。' : c.state === 'latest' ? '存下以后就算改过的出厂件:cotutor refresh 不再自动换新版,只报 diff。' : '改过的出厂件,cotutor refresh 不再自动换新版,只报 diff。'));
|
|
1879
1879
|
out.push(rule(), h('div', { class: 'srow' },
|
|
1880
1880
|
h('button', { class: 'btn danger', type: 'button', disabled: !r.diff.length && !dirtyCount(), on: { click: restoreActivityBtn } }, '恢复出厂'),
|
|
1881
1881
|
h('span', { class: 'note' }, c.state === 'latest' ? '磁盘上那份就是出厂的,恢复只是丢掉没存的改动。' : '换回出厂那份;你这份存成 ' + bakName(c) + '。' + (tf ? '别的老师、这位老师的 activity 都不碰。' : '别的 activity 不碰。'))));
|
|
@@ -1920,7 +1920,7 @@ ${pageDomJs(4000)}
|
|
|
1920
1920
|
out.push(h('div', { class: 'srow' }, h('span', { class: 'note', style: 'flex:1;min-width:160px' }, '这就是新话题里老师的系统提示:老师文件去掉 frontmatter' + (pv.board ? ',后面再接板书写法' : '') + '。'), h('button', { class: 'btn small', type: 'button', on: { click: (e) => copy(pv.text, e.target) } }, '复制全文')));
|
|
1921
1921
|
if (pv.rules) {
|
|
1922
1922
|
out.push(rule(), h('h3', {}, '话题第一条还带守则'),
|
|
1923
|
-
h('p', { class: 'note' }, '有脸的老师共用的 ' + pv.rules.path + ',机器件,cotutor
|
|
1923
|
+
h('p', { class: 'note' }, '有脸的老师共用的 ' + pv.rules.path + ',机器件,cotutor refresh 每次覆盖,这里不改。这位老师要不一样的做法,写进上面的老师文件,它优先。'),
|
|
1924
1924
|
h('details', {}, h('summary', {}, '看守则原文'), h('pre', { class: 'pv tool' }, pv.rules.text)));
|
|
1925
1925
|
}
|
|
1926
1926
|
out.push(rule(), h('p', { class: 'note' }, '档案、日记、计划这些每轮现拼进上下文包,不在这里;看一轮会拼成什么(不花钱):'),
|
|
@@ -1965,7 +1965,7 @@ ${pageDomJs(4000)}
|
|
|
1965
1965
|
const restoreActivityBtn = async () => {
|
|
1966
1966
|
const c = act.cur;
|
|
1967
1967
|
const tf = isTutorFile(c);
|
|
1968
|
-
const lead = c.state === 'latest' ? '磁盘上那份就是出厂的;没存的 ' + dirtyCount() + ' 处丢掉。' : '你这份存成 ' + bakName(c) + (dirtyCount() ? ',没存的 ' + dirtyCount() + ' 处一起丢掉' : '') + '。和 cotutor
|
|
1968
|
+
const lead = c.state === 'latest' ? '磁盘上那份就是出厂的;没存的 ' + dirtyCount() + ' 处丢掉。' : '你这份存成 ' + bakName(c) + (dirtyCount() ? ',没存的 ' + dirtyCount() + ' 处一起丢掉' : '') + '。和 cotutor reset ' + c.tutor + (tf ? '' : '/' + c.name) + ' 同一条路,' + (tf ? '别的老师不碰。' : '别的 activity 不碰。');
|
|
1969
1969
|
if (!(await ask('换回出厂那份?', [lead], '恢复出厂', '算了'))) return;
|
|
1970
1970
|
let r;
|
|
1971
1971
|
try { r = await api('POST', docUrl(c) + '/restore', {}); } catch (e) { alert(e.message); return; }
|
|
@@ -337,7 +337,7 @@ ${pageDomJs(3200)}
|
|
|
337
337
|
if (st.ready) rows.push(h('div', { class: 'it' }, h('span', { class: 'tx' }, h('b', {}, '配好了'), h('small', {}, (st.keyTail ? 'key …' + st.keyTail + ' · 业务空间 ' + st.workspaceId : '测试用的假百炼'))),
|
|
338
338
|
h('button', { type: 'button', class: 'x', disabled: BL.busy || null, on: { click: blTest } }, BL.busy && !BL.open ? '在试…' : '试连'),
|
|
339
339
|
st.canEdit ? h('button', { type: 'button', class: 'x', on: { click: () => { BL.open = !BL.open; BL.msg = ''; blDraw(); } } }, BL.open ? '收起' : '换一把') : null));
|
|
340
|
-
else rows.push(h('div', { class: 'box' }, h('b', {}, '还没配,孩子那边打不开'), h('div', { class: 'note', style: 'padding:0' }, '老师开口和想讲稿都走百炼,要百炼「按量付费」的 API key(控制台「密钥管理」里创建)和业务空间 id(控制台的业务空间详情里看);Token Plan 的 sk-sp-
|
|
340
|
+
else rows.push(h('div', { class: 'box' }, h('b', {}, '还没配,孩子那边打不开'), h('div', { class: 'note', style: 'padding:0' }, '老师开口和想讲稿都走百炼,要百炼「按量付费」的 API key(控制台「密钥管理」里创建)和业务空间 id(控制台的业务空间详情里看);Token Plan 的 sk-sp- 不行。存在这台电脑上,不进孩子的 cotutor 文件夹。')));
|
|
341
341
|
if (!st.ready || BL.open) {
|
|
342
342
|
if (!st.canEdit) rows.push(h('div', { class: 'box' }, h('div', { class: 'note', style: 'padding:0' }, '先设家长密码:在电脑上跑 cotutor pin;或者在这台电脑上打开家长端来配。')));
|
|
343
343
|
else rows.push(h('div', { class: 'box' }, h('label', { class: 'lbl', for: 'bl-key' }, 'API key'), blKey, h('label', { class: 'lbl', for: 'bl-ws' }, '业务空间 id'), blWs,
|
package/install.ps1
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# cotutor 一行安装(Windows,在 PowerShell 里粘贴):
|
|
2
|
+
# & ([scriptblock]::Create([Text.Encoding]::UTF8.GetString((iwr -useb https://cdn.jsdelivr.net/npm/cotutor@latest/install.ps1).Content)))
|
|
3
|
+
# (jsDelivr 不标编码,PowerShell 5.1 的 irm 会把中文按 Latin-1 读成乱码,所以自己按 UTF-8 解)
|
|
4
|
+
# 连不上 jsDelivr 就从 GitHub 取(GitHub 标了 utf-8,irm 就行):
|
|
2
5
|
# irm https://raw.githubusercontent.com/TarsLab/cotutor/main/install.ps1 | iex
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# 顺手建好孩子的文件夹、装好后台服务(短名 称呼 年级):
|
|
6
|
-
# & ([scriptblock]::Create((irm https://raw.githubusercontent.com/TarsLab/cotutor/main/install.ps1))) ming 小明 2
|
|
6
|
+
# 顺手建好孩子的文件夹、装好后台服务(短名 称呼 年级),最后接上就行:
|
|
7
|
+
# & ([scriptblock]::Create([Text.Encoding]::UTF8.GetString((iwr -useb https://cdn.jsdelivr.net/npm/cotutor@latest/install.ps1).Content))) ming 小明 2
|
|
7
8
|
#
|
|
8
9
|
# 只做这几件事:查 Node 的版本;npm 连不上就用国内镜像;装 cotutor。
|
|
9
10
|
# 不要管理员,不改执行策略:npm、cotutor 都用 .cmd 跑(PowerShell 缺省策略会拦 .ps1)。Node 没装或太旧时只说怎么装,不替你装。
|
package/install.sh
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# cotutor 一行安装(macOS、Linux):
|
|
3
|
-
# curl -fsSL https://raw.githubusercontent.com/TarsLab/cotutor/main/install.sh | sh
|
|
4
|
-
# 连不上 GitHub 就换这个:
|
|
5
3
|
# curl -fsSL https://cdn.jsdelivr.net/npm/cotutor@latest/install.sh | sh
|
|
4
|
+
# 连不上 jsDelivr 就从 GitHub 取:
|
|
5
|
+
# curl -fsSL https://raw.githubusercontent.com/TarsLab/cotutor/main/install.sh | sh
|
|
6
6
|
# 顺手建好孩子的文件夹、装好后台服务(短名 称呼 年级):
|
|
7
|
-
# curl -fsSL https://
|
|
7
|
+
# curl -fsSL https://cdn.jsdelivr.net/npm/cotutor@latest/install.sh | sh -s -- ming 小明 2
|
|
8
8
|
#
|
|
9
9
|
# 只做这几件事:查 Node 的版本;npm 连不上就用国内镜像;全局目录写不了就换到自己家目录下(不用 sudo);装 cotutor。
|
|
10
10
|
# 不用 sudo,不改系统设置。Node 没装或太旧时只说怎么装,不替你装。
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cotutor",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"releaseNotes": [
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
5
|
+
"让 AI 助手帮你装更省事:不用输网址,对它说「帮我在这台电脑上装 cotutor,先跑 npx cotutor@latest guide,照它说的一步步做」",
|
|
6
|
+
"孩子的笔记文件夹(档案、日记、老师的记忆)新装时放在 cotutor 文件夹旁边,也可以用你已有的 Obsidian 库;以前装的照旧能用",
|
|
7
|
+
"Linux 上的后台服务能起来了",
|
|
8
|
+
"命令更好懂:cotutor --help 只列命令,每条的用法和例子在它自己的帮助里;写错的选项会直接说哪里不对,不再悄悄忽略",
|
|
9
|
+
"换新出厂文件改叫 cotutor refresh,把改过的老师换回出厂的用 cotutor reset;升级还是 cotutor update"
|
|
9
10
|
],
|
|
10
11
|
"description": "AI tutors that co-teach with parents at home: real-time voice, an interactive whiteboard, and learning memory in your own Obsidian vault. Self-hosted; built for China's elementary curriculum.",
|
|
11
12
|
"type": "module",
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
"cards",
|
|
59
60
|
"skills",
|
|
60
61
|
"README.md",
|
|
62
|
+
"INSTALL.md",
|
|
61
63
|
"LICENSE",
|
|
62
64
|
"NOTICE",
|
|
63
65
|
"install.sh",
|
|
@@ -5,7 +5,7 @@ description: 在 cotutor workspace 里分析 AI 老师的对话:某一轮传了
|
|
|
5
5
|
|
|
6
6
|
<!-- 给人看
|
|
7
7
|
谁读:家长在 workspace 根开的编程助手
|
|
8
|
-
能改:机器件,
|
|
8
|
+
能改:机器件,refresh 覆盖;references/命令与文件.md 由 src/app/skills/analyze-doc.ts 生成
|
|
9
9
|
改了:在试用 workspace 里开编程助手问一句「老师为什么…」(《写提示词.md》§一)
|
|
10
10
|
-->
|
|
11
11
|
|
|
@@ -1,17 +1,175 @@
|
|
|
1
1
|
# 命令与文件(机器生成,别改)
|
|
2
2
|
|
|
3
|
-
## 分析用的命令(在 workspace
|
|
3
|
+
## 分析用的命令(在 workspace 根跑;每条就是 cotutor <命令> --help 的内容)
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
cotutor show <老师> <job> [<日期>] [--
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
这些命令都认 --workspace <目录>(在 workspace 根跑可以不写)和 --sandbox(在试用副本里跑,不进孩子的记录)。
|
|
7
|
+
|
|
8
|
+
cotutor show — 看一轮:问了什么、上下文包、讲稿与卡、读了什么
|
|
9
|
+
|
|
10
|
+
用法:
|
|
11
|
+
cotutor show <老师> <job> [<日期>] [--evals] [--json]
|
|
12
|
+
|
|
13
|
+
选项:
|
|
14
|
+
--evals 看回放的那轮(evals/ 里的)
|
|
15
|
+
--json 机器可读的输出
|
|
16
|
+
|
|
17
|
+
说明:
|
|
18
|
+
- 日期缺省今天;--json 和家长端「看原文」是同一份
|
|
19
|
+
|
|
20
|
+
例子:
|
|
21
|
+
cotutor show math-tutor 1620-1 2026-09-12 --json
|
|
22
|
+
|
|
23
|
+
cotutor pack — 干跑上下文包:不起模型,看老师会拿到什么
|
|
24
|
+
|
|
25
|
+
用法:
|
|
26
|
+
cotutor pack <老师> [<消息>] [--activity <名> [--param '名=值;名=值']] [--from kid|system] [--at <ISO时间>] [--json]
|
|
27
|
+
|
|
28
|
+
选项:
|
|
29
|
+
--activity <名> 像孩子点了首页的 activity 按钮,新开话题
|
|
30
|
+
--param '名=值;名=值' activity 的参数(按钮下面那几行)
|
|
31
|
+
--from kid|system 谁说的(缺省 kid;system 不算孩子说的)
|
|
32
|
+
--at <ISO时间> 当作这个时间
|
|
33
|
+
--json 机器可读的输出
|
|
34
|
+
|
|
35
|
+
说明:
|
|
36
|
+
- 每段来自哪个文件、一共几条、按政策带了几条;改了档案、日记、计划立刻看效果
|
|
37
|
+
|
|
38
|
+
例子:
|
|
39
|
+
cotutor pack math-tutor "讲讲退位"
|
|
40
|
+
|
|
41
|
+
cotutor replay — 同一问按现在的提示词再跑一遍,并排对照
|
|
42
|
+
|
|
43
|
+
用法:
|
|
44
|
+
cotutor replay <老师> <job> [<日期>] [--runtime <名>] [--json]
|
|
45
|
+
|
|
46
|
+
选项:
|
|
47
|
+
--runtime <名> 换个运行时
|
|
48
|
+
--json 机器可读的输出
|
|
49
|
+
|
|
50
|
+
说明:
|
|
51
|
+
- 落 evals/,不进孩子的对话、不配音
|
|
52
|
+
|
|
53
|
+
注意:
|
|
54
|
+
- 花钱
|
|
55
|
+
|
|
56
|
+
例子:
|
|
57
|
+
cotutor replay math-tutor 1620-1 2026-09-12
|
|
58
|
+
|
|
59
|
+
cotutor compare — 再看一次回放的对照
|
|
60
|
+
|
|
61
|
+
用法:
|
|
62
|
+
cotutor compare <老师> [<evalJob>] [<日期>] [--json]
|
|
63
|
+
|
|
64
|
+
选项:
|
|
65
|
+
--json 机器可读的输出
|
|
66
|
+
|
|
67
|
+
说明:
|
|
68
|
+
- 不给 evalJob 就列这天回放过哪些
|
|
69
|
+
|
|
70
|
+
例子:
|
|
71
|
+
cotutor compare math-tutor
|
|
72
|
+
|
|
73
|
+
cotutor trace — 回放一轮的事件
|
|
74
|
+
|
|
75
|
+
用法:
|
|
76
|
+
cotutor trace <老师> <job> [<日期>] [--lane <道>] [--json]
|
|
77
|
+
|
|
78
|
+
选项:
|
|
79
|
+
--lane <道> 只看这几道的事件,逗号分开,如 main,tts
|
|
80
|
+
--json 机器可读的输出
|
|
81
|
+
|
|
82
|
+
说明:
|
|
83
|
+
- 读 <日期>.<job>.events.jsonl;日期缺省今天
|
|
84
|
+
|
|
85
|
+
例子:
|
|
86
|
+
cotutor trace math-tutor 1620-1 --lane main
|
|
87
|
+
|
|
88
|
+
cotutor doctor — 体检;--fix 修掉能自动修的
|
|
89
|
+
|
|
90
|
+
用法:
|
|
91
|
+
cotutor doctor [--json]
|
|
92
|
+
cotutor doctor --fix
|
|
93
|
+
cotutor doctor --live
|
|
94
|
+
|
|
95
|
+
选项:
|
|
96
|
+
--fix 修掉能自动修的(幂等,不碰孩子的数据,不用人输东西),要人来的留着
|
|
97
|
+
--live 真连一次百炼的嘴、真配一句音
|
|
98
|
+
--json 机器可读的输出
|
|
99
|
+
|
|
100
|
+
说明:
|
|
101
|
+
- --json 里没过的项带 cmd(能直接跑)或 human(要人来)
|
|
102
|
+
- 有必需项没过,退出码 1
|
|
103
|
+
|
|
104
|
+
注意:
|
|
105
|
+
- --live 花一分钱
|
|
106
|
+
|
|
107
|
+
例子:
|
|
108
|
+
cotutor doctor --fix
|
|
109
|
+
cotutor doctor --json
|
|
110
|
+
|
|
111
|
+
cotutor send — 终端里给老师发一条
|
|
112
|
+
|
|
113
|
+
用法:
|
|
114
|
+
cotutor send <老师> [<消息>] [--new] [--photo <图,图>] [--quiet] [--json]
|
|
115
|
+
cotutor send <老师> --activity <名> [--param '名=值;名=值']
|
|
116
|
+
|
|
117
|
+
选项:
|
|
118
|
+
--new 开新话题
|
|
119
|
+
--photo <图,图> 带作业照片(jpg / png,最多 9 张);有照片时消息可以空
|
|
120
|
+
--activity <名> 像孩子点了首页的 activity 按钮,新开话题
|
|
121
|
+
--param '名=值;名=值' activity 的参数(按钮下面那几行)
|
|
122
|
+
--from kid|system 谁说的(缺省 kid;system 不算孩子说的)
|
|
123
|
+
--runtime <名> 换个运行时
|
|
124
|
+
--lane <道> 只看这几道的事件,逗号分开,如 main,tts
|
|
125
|
+
--quiet 只要结果,不打印过程
|
|
126
|
+
--json 机器可读的输出
|
|
127
|
+
|
|
128
|
+
说明:
|
|
129
|
+
- 孩子的话照实时写(同孩子端),只是没有嘴;事件落 events.jsonl,事后 cotutor trace 看
|
|
130
|
+
|
|
131
|
+
注意:
|
|
132
|
+
- 花钱
|
|
133
|
+
- 写进孩子的对话记录(试一下用 --sandbox)
|
|
134
|
+
|
|
135
|
+
例子:
|
|
136
|
+
cotutor send math-tutor "这一步为什么要借位"
|
|
137
|
+
|
|
138
|
+
cotutor rate — 给一个话题打星(1–5)
|
|
139
|
+
|
|
140
|
+
用法:
|
|
141
|
+
cotutor rate <老师> <话题> <1-5> [--date <日期>] [--json]
|
|
142
|
+
|
|
143
|
+
选项:
|
|
144
|
+
--date <日期> 哪天(YYYY-MM-DD,缺省今天)
|
|
145
|
+
--json 机器可读的输出
|
|
146
|
+
|
|
147
|
+
说明:
|
|
148
|
+
- 和家长端同一条路;到 vault.keepScore 星的话题,记账时摘要才进日记
|
|
149
|
+
|
|
150
|
+
例子:
|
|
151
|
+
cotutor rate math-tutor 1620-1 4
|
|
152
|
+
|
|
153
|
+
cotutor bookkeep — 记账:把这天的话题写进日记
|
|
154
|
+
|
|
155
|
+
用法:
|
|
156
|
+
cotutor bookkeep <老师> [--date <日期>] [--thread <话题>] [--quiet] [--json]
|
|
157
|
+
|
|
158
|
+
选项:
|
|
159
|
+
--date <日期> 哪天(YYYY-MM-DD,缺省今天)
|
|
160
|
+
--thread <话题> 只记这个话题
|
|
161
|
+
--quiet 不打印过程
|
|
162
|
+
--json 机器可读的输出
|
|
163
|
+
|
|
164
|
+
说明:
|
|
165
|
+
- 每个还没记过的话题起一轮记账,应用写进笔记文件夹的日记
|
|
166
|
+
|
|
167
|
+
注意:
|
|
168
|
+
- 花钱
|
|
169
|
+
- 写孩子的笔记文件夹
|
|
170
|
+
|
|
171
|
+
例子:
|
|
172
|
+
cotutor bookkeep math-tutor
|
|
15
173
|
```
|
|
16
174
|
|
|
17
175
|
## 一轮的文件(conversations/ 下;回放的在 evals/ 下,同一套名字)
|
|
@@ -5,7 +5,7 @@ description: 排 cotutor 孩子端的首页。孩子学完之后,家长在 works
|
|
|
5
5
|
|
|
6
6
|
<!-- 给人看
|
|
7
7
|
谁读:家长在 workspace 根开的编程助手
|
|
8
|
-
能改:机器件,
|
|
8
|
+
能改:机器件,refresh 覆盖;references/ 下的首页语法.md、命令.md、tutor.md 由 src/app/skills/home-doc.ts 与 cards/tutor/card.md 生成
|
|
9
9
|
改了:在试用 workspace 里开编程助手说一句「总结一下今天」(《写提示词.md》§一)
|
|
10
10
|
-->
|
|
11
11
|
|
|
@@ -1,14 +1,82 @@
|
|
|
1
1
|
# 命令(机器生成,别改)
|
|
2
2
|
|
|
3
|
-
在 workspace
|
|
3
|
+
在 workspace 根跑;每条就是 cotutor <命令> --help 的内容。
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
cotutor home
|
|
6
|
+
这些命令都认 --workspace <目录>(在 workspace 根跑可以不写)和 --sandbox(在试用副本里跑,不进孩子的记录)。
|
|
7
|
+
|
|
8
|
+
cotutor home — 检查和发布孩子端首页
|
|
9
|
+
|
|
10
|
+
用法:
|
|
11
|
+
cotutor home check [--published] [--json]
|
|
12
|
+
cotutor home publish [--force] [--from <文件>] [--json]
|
|
13
|
+
cotutor home show [--json]
|
|
14
|
+
|
|
15
|
+
选项:
|
|
16
|
+
--published [check] 查已发布的那份(缺省查草稿 home/draft.md)
|
|
17
|
+
--force [publish] 有要改的卡,丢掉它们照发
|
|
18
|
+
--from <文件> [publish] 发另一份(比如历史里的)
|
|
19
|
+
--json 机器可读的输出
|
|
20
|
+
|
|
21
|
+
说明:
|
|
22
|
+
- check 不花钱:孩子会看到哪几张卡、每位老师的按钮、要改的(带行号)
|
|
23
|
+
- publish 先检查,留历史 home/history/,写 home/published.json;孩子端刷新就是新的
|
|
24
|
+
- show:现在发布的是哪份、每个按钮被点了几次
|
|
25
|
+
|
|
26
|
+
例子:
|
|
27
|
+
cotutor home check
|
|
28
|
+
cotutor home publish
|
|
29
|
+
|
|
30
|
+
cotutor show — 看一轮:问了什么、上下文包、讲稿与卡、读了什么
|
|
31
|
+
|
|
32
|
+
用法:
|
|
33
|
+
cotutor show <老师> <job> [<日期>] [--evals] [--json]
|
|
34
|
+
|
|
35
|
+
选项:
|
|
36
|
+
--evals 看回放的那轮(evals/ 里的)
|
|
37
|
+
--json 机器可读的输出
|
|
38
|
+
|
|
39
|
+
说明:
|
|
40
|
+
- 日期缺省今天;--json 和家长端「看原文」是同一份
|
|
41
|
+
|
|
42
|
+
例子:
|
|
43
|
+
cotutor show math-tutor 1620-1 2026-09-12 --json
|
|
44
|
+
|
|
45
|
+
cotutor rate — 给一个话题打星(1–5)
|
|
46
|
+
|
|
47
|
+
用法:
|
|
48
|
+
cotutor rate <老师> <话题> <1-5> [--date <日期>] [--json]
|
|
49
|
+
|
|
50
|
+
选项:
|
|
51
|
+
--date <日期> 哪天(YYYY-MM-DD,缺省今天)
|
|
52
|
+
--json 机器可读的输出
|
|
53
|
+
|
|
54
|
+
说明:
|
|
55
|
+
- 和家长端同一条路;到 vault.keepScore 星的话题,记账时摘要才进日记
|
|
56
|
+
|
|
57
|
+
例子:
|
|
58
|
+
cotutor rate math-tutor 1620-1 4
|
|
59
|
+
|
|
60
|
+
cotutor bookkeep — 记账:把这天的话题写进日记
|
|
61
|
+
|
|
62
|
+
用法:
|
|
63
|
+
cotutor bookkeep <老师> [--date <日期>] [--thread <话题>] [--quiet] [--json]
|
|
64
|
+
|
|
65
|
+
选项:
|
|
66
|
+
--date <日期> 哪天(YYYY-MM-DD,缺省今天)
|
|
67
|
+
--thread <话题> 只记这个话题
|
|
68
|
+
--quiet 不打印过程
|
|
69
|
+
--json 机器可读的输出
|
|
70
|
+
|
|
71
|
+
说明:
|
|
72
|
+
- 每个还没记过的话题起一轮记账,应用写进笔记文件夹的日记
|
|
73
|
+
|
|
74
|
+
注意:
|
|
75
|
+
- 花钱
|
|
76
|
+
- 写孩子的笔记文件夹
|
|
77
|
+
|
|
78
|
+
例子:
|
|
79
|
+
cotutor bookkeep math-tutor
|
|
12
80
|
```
|
|
13
81
|
|
|
14
82
|
## 文件
|