superlab 0.1.5 → 0.1.7

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 CHANGED
@@ -22,6 +22,8 @@ cd your-project
22
22
  superlab init
23
23
  ```
24
24
 
25
+ After `init` or `update`, start a new Codex or Claude session. If the new `/lab:*` commands still do not show up, restart the app.
26
+
25
27
  If the package has not been published yet, install from GitHub instead:
26
28
 
27
29
  ```bash
@@ -90,6 +92,30 @@ superlab update --all-projects
90
92
 
91
93
  `superlab init` writes `.superlab/install.json` inside each project and registers the project in the user-level registry so `update --all-projects` knows what to refresh.
92
94
 
95
+ Legacy projects from older `superlab` versions may already contain `.codex/`, `.claude/`, or `.superlab/` assets without `.superlab/install.json`. `superlab update` now reconstructs that metadata and upgrades the project in place.
96
+
97
+ ## Version
98
+
99
+ Show the CLI version and the current project asset version:
100
+
101
+ ```bash
102
+ superlab version
103
+ ```
104
+
105
+ Show only the current project asset version:
106
+
107
+ ```bash
108
+ superlab version --project
109
+ ```
110
+
111
+ Show a specific project:
112
+
113
+ ```bash
114
+ superlab version --target /path/to/project
115
+ ```
116
+
117
+ If a project still uses an older pre-metadata installation, `superlab version` reports `project: legacy` until you run `superlab update`.
118
+
93
119
  ## Language
94
120
 
95
121
  The installer chooses the display language from your system locale. If it detects Chinese locale values, it installs Chinese-facing command and skill text; otherwise it falls back to English.
package/README.zh-CN.md CHANGED
@@ -20,6 +20,8 @@ cd your-project
20
20
  superlab init
21
21
  ```
22
22
 
23
+ 执行完 `init` 或 `update` 后,请新开一个 Codex 或 Claude 会话;如果新的 `/lab:*` 命令仍未生效,再重启应用。
24
+
23
25
  如果 npm 还没发布,也可以直接从 GitHub 安装:
24
26
 
25
27
  ```bash
@@ -88,6 +90,30 @@ superlab update --all-projects
88
90
 
89
91
  `superlab init` 会在项目内写入 `.superlab/install.json`,并在用户级 registry 里登记项目路径,所以 `update --all-projects` 才知道要刷新哪些项目。
90
92
 
93
+ 较老版本的 `superlab` 项目可能已经有 `.codex/`、`.claude/` 或 `.superlab/` 资产,但还没有 `.superlab/install.json`。现在 `superlab update` 会自动重建这份 metadata,然后原地升级该项目。
94
+
95
+ ## 版本查询
96
+
97
+ 查看当前 CLI 版本和当前目录项目的资产版本:
98
+
99
+ ```bash
100
+ superlab version
101
+ ```
102
+
103
+ 只看当前项目资产版本:
104
+
105
+ ```bash
106
+ superlab version --project
107
+ ```
108
+
109
+ 查看指定项目:
110
+
111
+ ```bash
112
+ superlab version --target /path/to/project
113
+ ```
114
+
115
+ 如果项目还是旧的 pre-metadata 安装,`superlab version` 会显示 `project: legacy`;执行一次 `superlab update` 后就会切换到正式版本号。
116
+
91
117
  ## 语言
92
118
 
93
119
  安装器会根据系统 locale 自动猜测展示语言。检测到中文 locale 时会安装中文命令和技能文案;否则默认安装英文文案。
package/bin/superlab.cjs CHANGED
@@ -2,13 +2,22 @@
2
2
 
3
3
  const path = require("node:path");
4
4
  const {
5
+ PACKAGE_VERSION,
5
6
  detectLanguage,
7
+ getProjectVersionInfo,
6
8
  installSuperlab,
7
9
  updateAllProjects,
8
10
  updateSuperlabProject,
9
11
  } = require("../lib/install.cjs");
10
12
  const { promptSelect } = require("../lib/init_tui.cjs");
11
13
 
14
+ function restartReminder(lang) {
15
+ if (lang === "zh") {
16
+ return "项目资产已更新。请新开一个 Codex/Claude 会话;如果仍未生效,再重启应用。";
17
+ }
18
+ return "Project assets updated. Start a new Codex/Claude session; if changes still do not appear, restart the app.";
19
+ }
20
+
12
21
  function printHelp() {
13
22
  console.log(`superlab
14
23
 
@@ -17,11 +26,13 @@ Usage:
17
26
  superlab install [--target <dir>] [--platform codex|claude|both|all] [--lang en|zh] [--force]
18
27
  superlab update [--target <dir>]
19
28
  superlab update --all-projects
29
+ superlab version [--target <dir>] [--global|--project]
20
30
 
21
31
  Commands:
22
32
  init Initialize /lab commands, skills, templates, and scripts in a target
23
33
  install Backward-compatible alias for init
24
34
  update Refresh an initialized project or all registered projects
35
+ version Show installed CLI version and project asset version
25
36
  help Show this help message
26
37
  `);
27
38
  }
@@ -89,6 +100,56 @@ function parseUpdateArgs(argv) {
89
100
  return options;
90
101
  }
91
102
 
103
+ function parseVersionArgs(argv) {
104
+ const options = {
105
+ targetDir: process.cwd(),
106
+ globalOnly: false,
107
+ projectOnly: false,
108
+ };
109
+
110
+ for (let index = 0; index < argv.length; index += 1) {
111
+ const value = argv[index];
112
+ if (value === "--target") {
113
+ options.targetDir = path.resolve(argv[index + 1]);
114
+ index += 1;
115
+ } else if (value === "--global") {
116
+ options.globalOnly = true;
117
+ } else if (value === "--project") {
118
+ options.projectOnly = true;
119
+ } else {
120
+ throw new Error(`Unknown option: ${value}`);
121
+ }
122
+ }
123
+
124
+ if (options.globalOnly && options.projectOnly) {
125
+ throw new Error("Use either --global or --project, not both.");
126
+ }
127
+
128
+ return options;
129
+ }
130
+
131
+ function printVersion(options) {
132
+ const lines = [];
133
+ if (!options.projectOnly) {
134
+ lines.push(`cli: ${PACKAGE_VERSION}`);
135
+ }
136
+ if (!options.globalOnly) {
137
+ const projectInfo = getProjectVersionInfo({ targetDir: options.targetDir });
138
+ if (projectInfo.status === "managed") {
139
+ lines.push(`project: ${projectInfo.package_version}`);
140
+ lines.push(`platform: ${projectInfo.platform}`);
141
+ lines.push(`language: ${projectInfo.lang}`);
142
+ } else if (projectInfo.status === "legacy") {
143
+ lines.push("project: legacy");
144
+ lines.push(`platform: ${projectInfo.platform}`);
145
+ lines.push(`language: ${projectInfo.lang}`);
146
+ } else {
147
+ lines.push("project: not initialized");
148
+ }
149
+ }
150
+ console.log(lines.join("\n"));
151
+ }
152
+
92
153
  function shouldUseInteractiveInit(options) {
93
154
  if (options.lang && options.platform) {
94
155
  return false;
@@ -140,10 +201,15 @@ async function main() {
140
201
  return;
141
202
  }
142
203
 
143
- if (!["init", "install", "update"].includes(command)) {
204
+ if (!["init", "install", "update", "version"].includes(command)) {
144
205
  throw new Error(`Unknown command: ${command}`);
145
206
  }
146
207
 
208
+ if (command === "version") {
209
+ printVersion(parseVersionArgs(rest));
210
+ return;
211
+ }
212
+
147
213
  if (command === "update") {
148
214
  const options = parseUpdateArgs(rest);
149
215
  if (options.allProjects) {
@@ -155,6 +221,7 @@ async function main() {
155
221
  for (const project of result.skipped) {
156
222
  console.log(`skipped: ${project.path} (${project.reason})`);
157
223
  }
224
+ console.log(restartReminder(detectLanguage()));
158
225
  return;
159
226
  }
160
227
 
@@ -162,6 +229,10 @@ async function main() {
162
229
  console.log(`superlab updated in ${options.targetDir}`);
163
230
  console.log(`platform: ${metadata.platform}`);
164
231
  console.log(`language: ${metadata.lang}`);
232
+ if (metadata.migration) {
233
+ console.log(`migration: ${metadata.migration}`);
234
+ }
235
+ console.log(restartReminder(metadata.lang));
165
236
  return;
166
237
  }
167
238
 
@@ -170,10 +241,11 @@ async function main() {
170
241
  if (options.platform === "all") {
171
242
  options.platform = "both";
172
243
  }
173
- installSuperlab(options);
244
+ installSuperlab({ ...options, registerProject: true });
174
245
  console.log(`superlab installed into ${options.targetDir}`);
175
246
  console.log(`platform: ${options.platform}`);
176
247
  console.log(`language: ${options.lang}`);
248
+ console.log(restartReminder(options.lang));
177
249
  }
178
250
 
179
251
  main().catch((error) => {
package/lib/install.cjs CHANGED
@@ -137,6 +137,76 @@ function readProjectInstallMetadata(targetDir) {
137
137
  return JSON.parse(fs.readFileSync(filePath, "utf8"));
138
138
  }
139
139
 
140
+ function detectLegacyPlatform(targetDir) {
141
+ const hasCodex =
142
+ fs.existsSync(path.join(targetDir, ".codex", "prompts")) ||
143
+ fs.existsSync(path.join(targetDir, ".codex", "skills", "lab"));
144
+ const hasClaude =
145
+ fs.existsSync(path.join(targetDir, ".claude", "commands", "lab")) ||
146
+ fs.existsSync(path.join(targetDir, ".claude", "skills", "lab"));
147
+
148
+ if (hasCodex && hasClaude) {
149
+ return "both";
150
+ }
151
+ if (hasCodex) {
152
+ return "codex";
153
+ }
154
+ if (hasClaude) {
155
+ return "claude";
156
+ }
157
+ return null;
158
+ }
159
+
160
+ function looksChinese(text) {
161
+ return /[\u3400-\u9fff]/.test(text);
162
+ }
163
+
164
+ function detectLegacyLanguage(targetDir) {
165
+ const workflowConfigPath = path.join(targetDir, ".superlab", "config", "workflow.json");
166
+ if (fs.existsSync(workflowConfigPath)) {
167
+ try {
168
+ const config = JSON.parse(fs.readFileSync(workflowConfigPath, "utf8"));
169
+ if (config.workflow_language === "zh" || config.workflow_language === "en") {
170
+ return config.workflow_language;
171
+ }
172
+ } catch {
173
+ // Fall through to file-content heuristics.
174
+ }
175
+ }
176
+
177
+ const probeFiles = [
178
+ path.join(targetDir, ".codex", "prompts", "lab-idea.md"),
179
+ path.join(targetDir, ".claude", "commands", "lab", "idea.md"),
180
+ path.join(targetDir, ".superlab", "templates", "idea.md"),
181
+ ];
182
+
183
+ for (const probeFile of probeFiles) {
184
+ if (!fs.existsSync(probeFile)) {
185
+ continue;
186
+ }
187
+ const content = fs.readFileSync(probeFile, "utf8");
188
+ return looksChinese(content) ? "zh" : "en";
189
+ }
190
+
191
+ return "en";
192
+ }
193
+
194
+ function detectLegacyInstallMetadata(targetDir) {
195
+ const platform = detectLegacyPlatform(targetDir);
196
+ if (!platform) {
197
+ return null;
198
+ }
199
+
200
+ return {
201
+ target_dir: path.resolve(targetDir),
202
+ platform,
203
+ lang: detectLegacyLanguage(targetDir),
204
+ package_version: "legacy",
205
+ updated_at: null,
206
+ legacy: true,
207
+ };
208
+ }
209
+
140
210
  function readRegistry({ env = process.env } = {}) {
141
211
  const filePath = registryFilePath({ env });
142
212
  if (!fs.existsSync(filePath)) {
@@ -170,6 +240,16 @@ function registerProjectInstall(targetDir, metadata, { env = process.env } = {})
170
240
  writeRegistry(registry, { env });
171
241
  }
172
242
 
243
+ function isTemporaryTestPath(targetDir) {
244
+ const resolvedTarget = path.resolve(targetDir);
245
+ const tmpRoot = path.resolve(os.tmpdir());
246
+ const relativeToTmp = path.relative(tmpRoot, resolvedTarget);
247
+ if (relativeToTmp.startsWith("..") || path.isAbsolute(relativeToTmp)) {
248
+ return false;
249
+ }
250
+ return path.basename(resolvedTarget).startsWith("superlab-");
251
+ }
252
+
173
253
  function detectLanguage({ explicitLang, env = process.env } = {}) {
174
254
  if (explicitLang) {
175
255
  if (!["en", "zh"].includes(explicitLang)) {
@@ -243,7 +323,14 @@ function localizeInstalledAssets(targetDir, lang) {
243
323
  }
244
324
  }
245
325
 
246
- function installSuperlab({ targetDir, platform = "both", force = false, lang, env = process.env } = {}) {
326
+ function installSuperlab({
327
+ targetDir,
328
+ platform = "both",
329
+ force = false,
330
+ lang,
331
+ env = process.env,
332
+ registerProject = false,
333
+ } = {}) {
247
334
  const groups = [];
248
335
  if (platform === "codex" || platform === "both") {
249
336
  groups.push(...ASSET_GROUPS.codex);
@@ -267,46 +354,112 @@ function installSuperlab({ targetDir, platform = "both", force = false, lang, en
267
354
  updated_at: new Date().toISOString(),
268
355
  };
269
356
  writeProjectInstallMetadata(targetDir, metadata);
270
- registerProjectInstall(targetDir, metadata, { env });
357
+ if (registerProject) {
358
+ registerProjectInstall(targetDir, metadata, { env });
359
+ }
360
+ return metadata;
271
361
  }
272
362
 
273
363
  function updateSuperlabProject({ targetDir, env = process.env } = {}) {
274
- const metadata = readProjectInstallMetadata(targetDir);
275
- installSuperlab({
364
+ let metadata;
365
+ let migratedLegacy = false;
366
+ try {
367
+ metadata = readProjectInstallMetadata(targetDir);
368
+ } catch (error) {
369
+ metadata = detectLegacyInstallMetadata(targetDir);
370
+ if (!metadata) {
371
+ throw error;
372
+ }
373
+ migratedLegacy = true;
374
+ }
375
+
376
+ const installedMetadata = installSuperlab({
276
377
  targetDir,
277
378
  platform: metadata.platform,
278
379
  lang: metadata.lang,
279
380
  force: true,
280
381
  env,
382
+ registerProject: true,
281
383
  });
282
- return metadata;
384
+ if (migratedLegacy) {
385
+ installedMetadata.migration = "legacy project metadata reconstructed";
386
+ }
387
+ return installedMetadata;
283
388
  }
284
389
 
285
390
  function updateAllProjects({ env = process.env } = {}) {
286
391
  const registry = readRegistry({ env });
287
392
  const updated = [];
288
393
  const skipped = [];
394
+ const retained = [];
289
395
 
290
396
  for (const project of registry.projects) {
291
397
  try {
398
+ if (isTemporaryTestPath(project.path)) {
399
+ skipped.push({ path: project.path, reason: "temporary path pruned from registry" });
400
+ continue;
401
+ }
292
402
  if (!fs.existsSync(project.path)) {
293
403
  skipped.push({ path: project.path, reason: "missing project path" });
294
404
  continue;
295
405
  }
296
- updateSuperlabProject({ targetDir: project.path, env });
406
+ const metadata = updateSuperlabProject({ targetDir: project.path, env });
297
407
  updated.push(project.path);
408
+ retained.push({
409
+ path: path.resolve(project.path),
410
+ platform: metadata.platform,
411
+ lang: metadata.lang,
412
+ package_version: metadata.package_version,
413
+ updated_at: metadata.updated_at,
414
+ });
298
415
  } catch (error) {
299
416
  skipped.push({ path: project.path, reason: error.message });
417
+ if (fs.existsSync(project.path)) {
418
+ retained.push(project);
419
+ }
300
420
  }
301
421
  }
302
422
 
423
+ writeRegistry({ projects: retained }, { env });
303
424
  return { updated, skipped };
304
425
  }
305
426
 
427
+ function getProjectVersionInfo({ targetDir } = {}) {
428
+ try {
429
+ const metadata = readProjectInstallMetadata(targetDir);
430
+ return {
431
+ status: "managed",
432
+ package_version: metadata.package_version,
433
+ platform: metadata.platform,
434
+ lang: metadata.lang,
435
+ };
436
+ } catch {
437
+ const legacyMetadata = detectLegacyInstallMetadata(targetDir);
438
+ if (!legacyMetadata) {
439
+ return {
440
+ status: "missing",
441
+ package_version: null,
442
+ platform: null,
443
+ lang: null,
444
+ };
445
+ }
446
+ return {
447
+ status: "legacy",
448
+ package_version: "legacy",
449
+ platform: legacyMetadata.platform,
450
+ lang: legacyMetadata.lang,
451
+ };
452
+ }
453
+ }
454
+
306
455
  module.exports = {
456
+ PACKAGE_VERSION,
307
457
  detectLanguage,
458
+ detectLegacyInstallMetadata,
308
459
  installSuperlab,
309
460
  installMetadataPath,
461
+ isTemporaryTestPath,
462
+ getProjectVersionInfo,
310
463
  readProjectInstallMetadata,
311
464
  registryFilePath,
312
465
  readRegistry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlab",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Strict /lab research workflow installer for Codex and Claude",
5
5
  "keywords": [
6
6
  "codex",