cli-aimlock 5.0.3 → 7.0.1

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/installer.mjs ADDED
@@ -0,0 +1,241 @@
1
+ /**
2
+ * 五个官方技能共用这一份安装器。packages/*-cli/installer.mjs 必须与本文件字节一致。
3
+ * 禁止第二套超时、第二套版本来源、第二套 bin 名。
4
+ */
5
+ import { copyFile, mkdir, writeFile } from 'node:fs/promises'
6
+ import { existsSync, readFileSync } from 'node:fs'
7
+ import { dirname, join, resolve } from 'node:path'
8
+ import { stdin, stdout } from 'node:process'
9
+ import { createInterface } from 'node:readline/promises'
10
+ import { fileURLToPath } from 'node:url'
11
+
12
+ export const LOOKUP_TIMEOUT_MS = 8000
13
+ export const CALL_TIMEOUT_MS = 120_000
14
+ const INSTALL_META = 'install-meta.json'
15
+
16
+ function asObject(value, label) {
17
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
18
+ throw new Error(`${label} must be an object`)
19
+ }
20
+ return value
21
+ }
22
+
23
+ function requiredString(value, label) {
24
+ const text = typeof value === 'string' ? value.trim() : ''
25
+ if (!text) throw new Error(`${label} is required`)
26
+ return text
27
+ }
28
+
29
+ export function loadOfficialSkillContext(packageRoot) {
30
+ const pkg = asObject(JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')), 'package.json')
31
+ const skill = asObject(JSON.parse(readFileSync(join(packageRoot, 'skill/skill.json'), 'utf8')), 'skill.json')
32
+ const npmName = requiredString(pkg.name, 'package.json name')
33
+ const packageVersion = requiredString(pkg.version, 'package.json version')
34
+ const displayName = requiredString(skill.displayName, 'skill.json displayName')
35
+ const skillName = requiredString(skill.name, 'skill.json name')
36
+ const schemaVersion = requiredString(skill.schemaVersion, 'skill.json schemaVersion')
37
+ const endpoint = requiredString(skill.endpoint, 'skill.json endpoint')
38
+ const skillVersion = requiredString(skill.version, 'skill.json version')
39
+ const runtimeCode = requiredString(endpoint.replace(/^https:\/\/cli\.tax\//, ''), 'runtime code')
40
+ if (!/^[A-Za-z0-9]{10}$/.test(runtimeCode)) {
41
+ throw new Error(`skill.json endpoint must be https://cli.tax/{10-char-code}: ${endpoint}`)
42
+ }
43
+ if (skillVersion.replace(/^v/i, '') !== packageVersion.replace(/^v/i, '')) {
44
+ throw new Error(`skill.json ${skillVersion} must match package.json ${packageVersion}`)
45
+ }
46
+ return {
47
+ packageRoot,
48
+ npmName,
49
+ packageVersion,
50
+ displayName,
51
+ skillName,
52
+ schemaVersion,
53
+ endpoint,
54
+ skillVersion,
55
+ runtimeCode,
56
+ latestEndpoint: `https://cli.tax/api/public/skills/${runtimeCode}`,
57
+ skillDir: join(packageRoot, 'skill'),
58
+ }
59
+ }
60
+
61
+ export function readInstallMeta(target) {
62
+ const path = join(target, INSTALL_META)
63
+ if (!existsSync(path)) return null
64
+ return asObject(JSON.parse(readFileSync(path, 'utf8')), INSTALL_META)
65
+ }
66
+
67
+ export function installTarget(skillName, explicit) {
68
+ if (explicit) return resolve(explicit)
69
+ const codexHome = process.env.CODEX_HOME?.trim()
70
+ if (codexHome) return join(codexHome, 'skills', skillName)
71
+ return join(process.cwd(), '.codex', 'skills', skillName)
72
+ }
73
+
74
+ export async function fetchLatestVersion(context) {
75
+ const response = await fetch(context.latestEndpoint, { signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) })
76
+ if (!response.ok) throw new Error(`cli.tax skill lookup failed: HTTP ${response.status}`)
77
+ const data = asObject(await response.json(), 'cli.tax skill lookup')
78
+ return {
79
+ version: requiredString(data.version, 'cli.tax skill lookup version'),
80
+ displayName: requiredString(data.displayName, 'cli.tax skill lookup displayName'),
81
+ }
82
+ }
83
+
84
+ export async function callOfficialSkill(context, operation, input) {
85
+ const requestId = `${context.npmName}-${Date.now()}`
86
+ const response = await fetch(context.endpoint, {
87
+ method: 'POST',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({
90
+ input: {
91
+ schemaVersion: context.schemaVersion,
92
+ requestId,
93
+ operation,
94
+ input,
95
+ },
96
+ }),
97
+ signal: AbortSignal.timeout(CALL_TIMEOUT_MS),
98
+ })
99
+ let payload
100
+ try {
101
+ payload = await response.json()
102
+ } catch {
103
+ throw new Error(`${context.displayName} ${operation} failed: non-JSON response (HTTP ${response.status}). Check ${context.endpoint}.`)
104
+ }
105
+ if (!response.ok || payload?.ok !== true) {
106
+ const message = payload?.error?.message
107
+ if (typeof message !== 'string' || !message.trim()) {
108
+ throw new Error(`${context.displayName} ${operation} failed: HTTP ${response.status}`)
109
+ }
110
+ throw new Error(`${context.displayName} ${operation} failed: ${message}`)
111
+ }
112
+ return payload
113
+ }
114
+
115
+ export async function installOfficialSkill(context, explicit) {
116
+ const target = installTarget(context.skillName, explicit)
117
+ await mkdir(target, { recursive: true })
118
+ const previous = readInstallMeta(target)
119
+ await copyFile(join(context.skillDir, 'SKILL.md'), join(target, 'SKILL.md'))
120
+ await copyFile(join(context.skillDir, 'skill.json'), join(target, 'skill.json'))
121
+ const installed = asObject(JSON.parse(readFileSync(join(target, 'skill.json'), 'utf8')), 'installed skill.json')
122
+ const installedVersion = requiredString(installed.version, 'installed skill.json version')
123
+ await writeFile(join(target, INSTALL_META), `${JSON.stringify({
124
+ source: context.runtimeCode,
125
+ slug: context.skillName,
126
+ version: installedVersion,
127
+ packageVersion: context.packageVersion,
128
+ endpoint: context.endpoint,
129
+ installedAt: new Date().toISOString(),
130
+ }, null, 2)}\n`)
131
+ if (previous?.version && previous.version !== installedVersion) {
132
+ console.log(`${context.displayName} skill updated: ${target}`)
133
+ console.log(` ${previous.version} → ${installedVersion}`)
134
+ } else {
135
+ console.log(`${context.displayName} skill installed: ${target} (${installedVersion})`)
136
+ }
137
+ console.log('Next: return to your IDE and state the goal. The agent reads the installed SKILL.md.')
138
+ }
139
+
140
+ export async function checkOfficialSkill(context, explicit) {
141
+ const target = installTarget(context.skillName, explicit)
142
+ const current = readInstallMeta(target)
143
+ if (!current) {
144
+ console.log(`${context.displayName} skill is not installed. Run: npx ${context.npmName}@latest install`)
145
+ process.exitCode = 1
146
+ return
147
+ }
148
+ const installedVersion = requiredString(current.version, 'install-meta.json version')
149
+ const packageVersion = requiredString(current.packageVersion, 'install-meta.json packageVersion')
150
+ console.log(`Installed: ${installedVersion} (package ${packageVersion})`)
151
+ const latest = await fetchLatestVersion(context)
152
+ console.log(`Latest on cli.tax: ${latest.version}`)
153
+ if (installedVersion === latest.version) {
154
+ console.log('Up to date.')
155
+ return
156
+ }
157
+ console.log(`Update available: ${installedVersion} → ${latest.version}`)
158
+ console.log(`Run: npx ${context.npmName}@latest install`)
159
+ process.exitCode = 1
160
+ }
161
+
162
+ export function defaultUsage(context, extraLines) {
163
+ const lines = [
164
+ `${context.npmName} — install and run the ${context.displayName} skill from CLI.Tax`,
165
+ '',
166
+ 'Usage:',
167
+ ` npx ${context.npmName}@latest install [directory]`,
168
+ ` Install the ${context.displayName} skill for the current IDE.`,
169
+ ` npx ${context.npmName}@latest check [directory]`,
170
+ ' Check whether the installed skill has a newer version.',
171
+ ` npx ${context.npmName}@latest run`,
172
+ ' Run the skill handshake: discover capabilities and collect intake answers.',
173
+ '',
174
+ `Endpoint: ${context.endpoint}`,
175
+ ]
176
+ if (extraLines?.length) lines.push('', ...extraLines)
177
+ return lines.join('\n')
178
+ }
179
+
180
+ export async function runIntakeHandshake(context, spec) {
181
+ const capabilities = await callOfficialSkill(context, 'capabilities', {})
182
+ const output = capabilities.output && typeof capabilities.output === 'object' ? capabilities.output : {}
183
+ const skill = output.skill && typeof output.skill === 'object' ? output.skill : {}
184
+ const version = typeof skill.version === 'string' && skill.version.trim()
185
+ ? skill.version.trim()
186
+ : context.skillVersion
187
+ console.log(`${context.displayName} ${version}`)
188
+ if (typeof spec.afterCapabilities === 'function') spec.afterCapabilities(output)
189
+ const readline = createInterface({ input: stdin, output: stdout })
190
+ const answers = []
191
+ try {
192
+ for (const question of spec.questions) {
193
+ const requiredMark = question.required ? ' (required)' : ''
194
+ console.log(`\n${question.prompt}${requiredMark}`)
195
+ console.log(`Example: ${question.example}`)
196
+ for (;;) {
197
+ const answer = (await readline.question('> ')).trim()
198
+ if (answer) {
199
+ answers.push({ id: question.id, prompt: question.prompt, answer })
200
+ break
201
+ }
202
+ if (!question.required) break
203
+ console.log('This question is required. Please answer before continuing.')
204
+ }
205
+ }
206
+ } finally {
207
+ readline.close()
208
+ }
209
+ const target = join(process.cwd(), spec.outputFile)
210
+ await writeFile(target, `${JSON.stringify({
211
+ schemaVersion: context.schemaVersion,
212
+ endpoint: context.endpoint,
213
+ createdAt: new Date().toISOString(),
214
+ answers,
215
+ }, null, 2)}\n`)
216
+ console.log(`\nRequirements saved: ${target}`)
217
+ console.log('Next: continue in your IDE agent with this file.')
218
+ }
219
+
220
+ export async function dispatchOfficialSkillCli(options) {
221
+ const packageRoot = options.packageRoot ?? dirname(fileURLToPath(options.importMetaUrl))
222
+ const context = loadOfficialSkillContext(packageRoot)
223
+ const args = process.argv.slice(2)
224
+ const command = args[0] ?? 'help'
225
+ const argument = args[1]
226
+ try {
227
+ if (command === 'install') await installOfficialSkill(context, argument)
228
+ else if (command === 'check') await checkOfficialSkill(context, argument)
229
+ else if (command === 'run') await options.runCommand(context)
230
+ else if (command === 'help' || command === '--help' || command === '-h') {
231
+ console.log(options.usage ? options.usage(context) : defaultUsage(context, options.extraUsageLines))
232
+ } else {
233
+ console.error(`Unknown command: ${command}`)
234
+ console.log(options.usage ? options.usage(context) : defaultUsage(context, options.extraUsageLines))
235
+ process.exitCode = 1
236
+ }
237
+ } catch (error) {
238
+ console.error(error instanceof Error ? error.message : error)
239
+ process.exitCode = 1
240
+ }
241
+ }
package/package.json CHANGED
@@ -5,17 +5,17 @@
5
5
  "description": "Aimlock skill installer for CLI.Tax: lock a user request into an executable aim and route Blueprint, Swarm, and Calctool.",
6
6
  "files": [
7
7
  "cli.mjs",
8
+ "installer.mjs",
8
9
  "README.md",
9
10
  "skill/SKILL.md",
10
11
  "skill/skill.json"
11
12
  ],
12
13
  "license": "UNLICENSED",
13
14
  "name": "cli-aimlock",
14
- "private": false,
15
15
  "repository": {
16
16
  "type": "git",
17
17
  "url": "https://github.com/88208555/aimlock-clitax.git"
18
18
  },
19
19
  "type": "module",
20
- "version": "5.0.3"
20
+ "version": "7.0.1"
21
21
  }
package/skill/skill.json CHANGED
@@ -6,5 +6,5 @@
6
6
  "name": "aimlock",
7
7
  "schemaVersion": "aimlock.skill.request/1.0",
8
8
  "type": "Skill",
9
- "version": "v5.0.3"
10
- }
9
+ "version": "v7.0.1"
10
+ }