@entrorove/skill 0.1.2 → 0.1.21

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/release.mjs ADDED
@@ -0,0 +1,145 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { readFile, writeFile, rename, rm } from 'node:fs/promises'
3
+ import path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const directory = path.dirname(fileURLToPath(import.meta.url))
7
+ const sourceFiles = [
8
+ 'cli.mjs', 'version.mjs', 'SKILL.md', 'AGREEMENT.md',
9
+ 'package.json', 'manifest.json', 'release.mjs',
10
+ 'local-cli.mjs', 'local-state-lock.mjs', 'local-execution.mjs', 'local-integrity.mjs', 'local-formula.mjs', 'local-ledger.mjs',
11
+ 'local-approved-plan.mjs', 'local-budget-renewal.mjs', 'local-probe.mjs',
12
+ 'local-task-routing.mjs', 'remote-client.mjs',
13
+ ]
14
+ const stateFile = 'release-state.json'
15
+ const changelogFile = 'CHANGELOG.md'
16
+ const releasesFile = 'releases.json'
17
+
18
+ async function read(name) {
19
+ return readFile(path.join(directory, name), 'utf8')
20
+ }
21
+
22
+ function digest(value) {
23
+ return createHash('sha256').update(value).digest('hex')
24
+ }
25
+
26
+ function normalized(name, text) {
27
+ if (name === 'SKILL.md') {
28
+ return text.replace(/^Version: \d+\.\d+\.\d+\.$/m, 'Version: <release>.')
29
+ }
30
+ if (name === 'package.json' || name === 'manifest.json') {
31
+ const metadata = JSON.parse(text)
32
+ metadata.version = '<release>'
33
+ return JSON.stringify(metadata)
34
+ }
35
+ return text
36
+ }
37
+
38
+ async function sourceDigests() {
39
+ const entries = await Promise.all(sourceFiles.map(async name =>
40
+ [name, digest(normalized(name, await read(name)))]))
41
+ return Object.fromEntries(entries)
42
+ }
43
+
44
+ function versionNext(version) {
45
+ const parts = version.split('.').map(Number)
46
+ if (parts.length !== 3 || parts.some(part => !Number.isSafeInteger(part) || part < 0)) {
47
+ throw new Error('Skill version is not semantic version MAJOR.MINOR.PATCH')
48
+ }
49
+ return `${parts[0]}.${parts[1]}.${parts[2] + 1}`
50
+ }
51
+
52
+ function releaseNote(changed) {
53
+ const suppliedZh = process.env.ENTROROVE_RELEASE_NOTE_ZH?.trim()
54
+ const suppliedEn = process.env.ENTROROVE_RELEASE_NOTE_EN?.trim()
55
+ if (Boolean(suppliedZh) !== Boolean(suppliedEn)) {
56
+ throw new Error('Release notes must provide both Chinese and English')
57
+ }
58
+ if (suppliedZh && suppliedEn) return {
59
+ zh: suppliedZh.replaceAll(/\s+/g, ' '), en: suppliedEn.replaceAll(/\s+/g, ' '),
60
+ }
61
+ const labels = {
62
+ 'cli.mjs': { zh: '技能运行和受管安装', en: 'skill runtime and managed installation' },
63
+ 'version.mjs': { zh: '版本安全检查', en: 'version safety checks' },
64
+ 'SKILL.md': { zh: 'IDE 工作流程说明', en: 'IDE workflow instructions' },
65
+ 'AGREEMENT.md': { zh: '使用、隐私与推广告知', en: 'usage, privacy, and promotion notice' },
66
+ 'package.json': { zh: '安装包配置', en: 'package configuration' },
67
+ 'manifest.json': { zh: '技能清单', en: 'skill manifest' },
68
+ 'release.mjs': { zh: '自动版本与更新日志', en: 'automatic versioning and changelog' },
69
+ 'local-cli.mjs': { zh: '本地执行命令', en: 'local execution command' },
70
+ 'local-state-lock.mjs': { zh: '本地状态并发锁', en: 'local state concurrency lock' },
71
+ 'local-execution.mjs': { zh: '本地执行闸门与预算', en: 'local execution gates and budgets' },
72
+ 'local-integrity.mjs': { zh: '证据与回执验签', en: 'evidence and receipt verification' },
73
+ 'local-formula.mjs': { zh: '确定性公式引擎', en: 'deterministic formula engine' },
74
+ 'local-ledger.mjs': { zh: '执行与链路台账', en: 'execution and chain ledger' },
75
+ 'local-approved-plan.mjs': { zh: '冻结计划执行器', en: 'frozen plan runner' },
76
+ 'local-budget-renewal.mjs': { zh: '预算续期确认', en: 'budget renewal confirmation' },
77
+ 'local-probe.mjs': { zh: '范围探测与分档', en: 'scope probing and tiering' },
78
+ 'local-task-routing.mjs': { zh: '本地任务路由', en: 'local task routing' },
79
+ 'remote-client.mjs': { zh: '远端恢复客户端', en: 'remote recovery client' },
80
+ }
81
+ return {
82
+ zh: `更新了${changed.map(name => labels[name].zh).join('、')}。`,
83
+ en: `Updated ${changed.map(name => labels[name].en).join(', ')}.`,
84
+ }
85
+ }
86
+
87
+ async function writeAll(changes) {
88
+ const originals = new Map()
89
+ const staged = []
90
+ try {
91
+ for (const [name, content] of changes) {
92
+ originals.set(name, await read(name))
93
+ const temporary = path.join(directory, `.${name}.${process.pid}.tmp`)
94
+ await writeFile(temporary, content, { flag: 'wx', mode: 0o600 })
95
+ staged.push([name, temporary])
96
+ }
97
+ for (const [name, temporary] of staged) await rename(temporary, path.join(directory, name))
98
+ } catch (error) {
99
+ for (const [name, content] of originals) await writeFile(path.join(directory, name), content)
100
+ for (const [, temporary] of staged) await rm(temporary, { force: true })
101
+ throw error
102
+ }
103
+ }
104
+
105
+ const metadata = JSON.parse(await read('package.json'))
106
+ const manifest = JSON.parse(await read('manifest.json'))
107
+ const state = JSON.parse(await read(stateFile))
108
+ const skill = await read('SKILL.md')
109
+ if (metadata.version !== manifest.version || metadata.version !== state.version
110
+ || !skill.includes(`Version: ${metadata.version}.`)) {
111
+ throw new Error('Skill package, manifest, instruction, and release state versions differ')
112
+ }
113
+ const files = await sourceDigests()
114
+ const currentDigest = digest(JSON.stringify(files))
115
+ if (currentDigest !== state.digest) {
116
+ const changed = sourceFiles.filter(name => files[name] !== state.files[name])
117
+ if (changed.length === 0) throw new Error('Skill source digest changed without a changed file')
118
+ const next = versionNext(metadata.version)
119
+ metadata.version = next
120
+ manifest.version = next
121
+ const nextSkill = skill.replace(`Version: ${state.version}.`, `Version: ${next}.`)
122
+ const changelog = await read(changelogFile)
123
+ const date = new Date().toISOString().slice(0, 10)
124
+ const notes = releaseNote(changed)
125
+ const entry = `## ${next} — ${date}\n\n- ${notes.zh}\n- ${notes.en}\n\n`
126
+ const nextLog = changelog.replace('# Changelog\n\n', `# Changelog\n\n${entry}`)
127
+ if (nextLog === changelog) throw new Error('Changelog header is missing')
128
+ const releases = JSON.parse(await read(releasesFile))
129
+ if (!Array.isArray(releases) || releases[0]?.version !== state.version) {
130
+ throw new Error('Release index does not match current skill version')
131
+ }
132
+ const nextState = { schemaVersion: 'entrorove.release-state/1.0', version: next,
133
+ digest: currentDigest, files }
134
+ await writeAll([
135
+ ['package.json', JSON.stringify(metadata, null, 2) + '\n'],
136
+ ['manifest.json', JSON.stringify(manifest, null, 2) + '\n'],
137
+ ['SKILL.md', nextSkill],
138
+ [changelogFile, nextLog],
139
+ [releasesFile, JSON.stringify([{ version: next, date, ...notes }, ...releases], null, 2) + '\n'],
140
+ [stateFile, JSON.stringify(nextState, null, 2) + '\n'],
141
+ ])
142
+ process.stdout.write(`entrorove skill ${next}: ${notes.en}\n`)
143
+ } else {
144
+ process.stdout.write(`entrorove skill ${state.version}: no source change\n`)
145
+ }
package/releases.json ADDED
@@ -0,0 +1,122 @@
1
+ [
2
+ {
3
+ "version": "0.1.21",
4
+ "date": "2026-10-04",
5
+ "zh": "更新了本地执行闸门与预算。",
6
+ "en": "Updated local execution gates and budgets."
7
+ },
8
+ {
9
+ "version": "0.1.20",
10
+ "date": "2026-10-04",
11
+ "zh": "更新了本地状态并发锁、本地执行闸门与预算、执行与链路台账。",
12
+ "en": "Updated local state concurrency lock, local execution gates and budgets, execution and chain ledger."
13
+ },
14
+ {
15
+ "version": "0.1.19",
16
+ "date": "2026-10-04",
17
+ "zh": "更新了安装包配置、自动版本与更新日志、本地状态并发锁、本地执行闸门与预算、预算续期确认、本地任务路由。",
18
+ "en": "Updated package configuration, automatic versioning and changelog, local state concurrency lock, local execution gates and budgets, budget renewal confirmation, local task routing."
19
+ },
20
+ {
21
+ "version": "0.1.18",
22
+ "date": "2026-10-04",
23
+ "zh": "更新了冻结计划执行器。",
24
+ "en": "Updated frozen plan runner."
25
+ },
26
+ {
27
+ "version": "0.1.17",
28
+ "date": "2026-10-04",
29
+ "zh": "更新了本地执行命令、冻结计划执行器。",
30
+ "en": "Updated local execution command, frozen plan runner."
31
+ },
32
+ {
33
+ "version": "0.1.16",
34
+ "date": "2026-10-04",
35
+ "zh": "更新了远端恢复客户端。",
36
+ "en": "Updated remote recovery client."
37
+ },
38
+ {
39
+ "version": "0.1.15",
40
+ "date": "2026-10-04",
41
+ "zh": "更新了IDE 工作流程说明。",
42
+ "en": "Updated IDE workflow instructions."
43
+ },
44
+ {
45
+ "version": "0.1.14",
46
+ "date": "2026-10-02",
47
+ "zh": "更新了本地执行闸门与预算。",
48
+ "en": "Updated local execution gates and budgets."
49
+ },
50
+ {
51
+ "version": "0.1.13",
52
+ "date": "2026-10-02",
53
+ "zh": "更新了执行与链路台账、预算续期确认、本地任务路由。",
54
+ "en": "Updated execution and chain ledger, budget renewal confirmation, local task routing."
55
+ },
56
+ {
57
+ "version": "0.1.12",
58
+ "date": "2026-10-02",
59
+ "zh": "更新了远端恢复客户端。",
60
+ "en": "Updated remote recovery client."
61
+ },
62
+ {
63
+ "version": "0.1.11",
64
+ "date": "2026-10-02",
65
+ "zh": "更新了本地执行闸门与预算。",
66
+ "en": "Updated local execution gates and budgets."
67
+ },
68
+ {
69
+ "version": "0.1.10",
70
+ "date": "2026-10-02",
71
+ "zh": "更新了本地执行闸门与预算。",
72
+ "en": "Updated local execution gates and budgets."
73
+ },
74
+ {
75
+ "version": "0.1.9",
76
+ "date": "2026-10-02",
77
+ "zh": "更新了自动版本与更新日志、本地执行闸门与预算、证据与回执验签、执行与链路台账、冻结计划执行器。",
78
+ "en": "Updated automatic versioning and changelog, local execution gates and budgets, evidence and receipt verification, execution and chain ledger, frozen plan runner."
79
+ },
80
+ {
81
+ "version": "0.1.8",
82
+ "date": "2026-10-02",
83
+ "zh": "恢复本地执行层与质量闭环:快照隔离、签名写入、预算、协作、验证回执、公式和任务路由。",
84
+ "en": "Restored the local execution and quality workflow: snapshots, signed writes, budgets, coordination, receipts, formulas, and task routing."
85
+ },
86
+ {
87
+ "version": "0.1.7",
88
+ "date": "2026-10-02",
89
+ "zh": "恢复统一技能全集、本地执行层与安全复制流程。",
90
+ "en": "Restored the complete unified skill set, local execution layer, and secure copy flow."
91
+ },
92
+ {
93
+ "version": "0.1.6",
94
+ "date": "2026-09-29",
95
+ "zh": "配置密钥前检查凭据目录,拒绝符号链接目录,避免密钥写入非预期位置。",
96
+ "en": "Reject symlinked credential directories before writing a key to prevent storage at an unintended location."
97
+ },
98
+ {
99
+ "version": "0.1.5",
100
+ "date": "2026-09-29",
101
+ "zh": "发布前自动检测安装包、技能说明与清单变更,递增版本并同步更新日志;凭据继续跨升级留存。",
102
+ "en": "Automatically detect package, skill instruction, and manifest changes before publishing, increment the version, and update release notes while retaining credentials across upgrades."
103
+ },
104
+ {
105
+ "version": "0.1.4",
106
+ "date": "2026-09-29",
107
+ "zh": "品牌展示统一为小写 entrorove,保留原有密钥存储路径以确保升级不丢失凭据。",
108
+ "en": "Standardized the displayed brand as lowercase entrorove while preserving existing credential storage paths across upgrades."
109
+ },
110
+ {
111
+ "version": "0.1.3",
112
+ "date": "2026-09-29",
113
+ "zh": "安装后密钥持久留存;升级时只保留一个受管技能并阻止旧版覆盖,同时明确说明按连续使用时长与调用次数调整推广概率。",
114
+ "en": "Keep the key across upgrades, preserve one managed skill and reject downgrades, and disclose usage-based promotion timing."
115
+ },
116
+ {
117
+ "version": "0.1.2",
118
+ "date": "2026-09-29",
119
+ "zh": "已加入统一技能安装、持久化账号密钥和首次使用确认。",
120
+ "en": "Added the unified skill installer, persistent account key, and explicit first-use agreement."
121
+ }
122
+ ]
@@ -0,0 +1,91 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ export const apiUrl = 'https://entrorove.com/api/skill/invoke';
4
+
5
+ function canonical(value) {
6
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
7
+ if (value && typeof value === 'object') {
8
+ return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
9
+ }
10
+ return JSON.stringify(value);
11
+ }
12
+
13
+ function requestId(operation, input) {
14
+ return createHash('sha256').update(operation + canonical(input)).digest('hex');
15
+ }
16
+
17
+ export function buildInvocation(operation, input) {
18
+ if (!['capabilities', 'task-describe', 'message-route', 'message-accept', 'task-resume', 'start', 'continue'].includes(operation)) throw new Error('Invalid entrorove operation');
19
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Input must be a JSON object');
20
+ const id = requestId(operation, input);
21
+ return { url: apiUrl, requestId: id, body: { schemaVersion: 'entrorove.skill.request/2.0', requestId: id, operation, input } };
22
+ }
23
+
24
+ async function readResponse(response, label) {
25
+ try { return await response.json(); } catch { throw new Error(`Invalid ${label} response`); }
26
+ }
27
+
28
+ function preTlsRetryable(error) {
29
+ const code = error?.cause?.code ?? error?.code;
30
+ return ['ECONNRESET', 'ECONNREFUSED', 'EAI_AGAIN', 'ENETUNREACH', 'ENETDOWN',
31
+ 'EHOSTUNREACH', 'ETIMEDOUT', 'ECONNABORTED', 'UND_ERR_CONNECT_TIMEOUT'].includes(code);
32
+ }
33
+
34
+ async function readOnlyGet(url, headers, label) {
35
+ let last;
36
+ for (let attempt = 0; attempt < 3; attempt += 1) {
37
+ try {
38
+ return await fetch(url, { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(15_000), headers });
39
+ } catch (error) {
40
+ last = error;
41
+ if (!preTlsRetryable(error) || attempt === 2) break;
42
+ }
43
+ }
44
+ throw new Error(`Unable to read ${label}; ${last instanceof Error ? last.message : 'network error'}`);
45
+ }
46
+
47
+ export async function invokeRemote(data, dependencies) {
48
+ const { install, agreementAccepted, agreementRequired, readCredential, home, apiOrigin } = dependencies;
49
+ await install();
50
+ if (!await agreementAccepted(home)) {
51
+ process.stdout.write(JSON.stringify(agreementRequired(home)) + '\n');
52
+ process.exitCode = 2;
53
+ return;
54
+ }
55
+ const call = buildInvocation(data.operation, data.input);
56
+ const token = await readCredential(home);
57
+ let response;
58
+ try {
59
+ response = await fetch(call.url, {
60
+ method: 'POST', redirect: 'error', signal: AbortSignal.timeout(135_000),
61
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
62
+ body: JSON.stringify(call.body),
63
+ });
64
+ } catch {
65
+ throw new Error(`Invocation outcome is unknown; requestId=${call.requestId}. Do not automatically resend this request.`);
66
+ }
67
+ const result = await readResponse(response, 'invocation');
68
+ if (!result || !result.output || result.output.requestId !== call.requestId) {
69
+ throw new Error(`Invalid entrorove response; requestId=${call.requestId}`);
70
+ }
71
+ process.stdout.write(JSON.stringify(result) + '\n');
72
+ if (!response.ok || result.output.status === 'blocked' || result.output.status === 'failed'
73
+ || result.output.status === 'incomplete') process.exitCode = 2;
74
+ }
75
+
76
+ export async function queryStatus(requestId, dependencies) {
77
+ if (typeof requestId !== 'string' || !/^[a-f0-9]{64}$/.test(requestId)) throw new Error('status requires a sha256 requestId');
78
+ const token = await dependencies.readCredential(dependencies.home);
79
+ const response = await readOnlyGet(`${dependencies.apiOrigin()}/api/skill/status/${encodeURIComponent(requestId)}`, { authorization: `Bearer ${token}` }, 'status')
80
+ const result = await readResponse(response, 'status');
81
+ if (!response.ok) throw new Error(`entrorove status failed: HTTP ${response.status}; requestId=${requestId}`);
82
+ process.stdout.write(JSON.stringify(result) + '\n');
83
+ }
84
+
85
+ export async function queryFlows(dependencies) {
86
+ const token = await dependencies.readCredential(dependencies.home);
87
+ const response = await readOnlyGet(`${dependencies.apiOrigin()}/api/skill/flows`, { authorization: `Bearer ${token}` }, 'flow list')
88
+ const result = await readResponse(response, 'flow list');
89
+ if (!response.ok) throw new Error(`entrorove flow list failed: HTTP ${response.status}`);
90
+ process.stdout.write(JSON.stringify(result) + '\n');
91
+ }
package/version.mjs ADDED
@@ -0,0 +1,8 @@
1
+ export function compareVersions(left, right) {
2
+ const a = left.split('.').map(Number);
3
+ const b = right.split('.').map(Number);
4
+ for (let index = 0; index < 3; index += 1) {
5
+ if (a[index] !== b[index]) return a[index] - b[index];
6
+ }
7
+ return 0;
8
+ }