superlab 0.1.5 → 0.1.6

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/bin/superlab.cjs CHANGED
@@ -2,7 +2,9 @@
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,
@@ -17,11 +19,13 @@ Usage:
17
19
  superlab install [--target <dir>] [--platform codex|claude|both|all] [--lang en|zh] [--force]
18
20
  superlab update [--target <dir>]
19
21
  superlab update --all-projects
22
+ superlab version [--target <dir>] [--global|--project]
20
23
 
21
24
  Commands:
22
25
  init Initialize /lab commands, skills, templates, and scripts in a target
23
26
  install Backward-compatible alias for init
24
27
  update Refresh an initialized project or all registered projects
28
+ version Show installed CLI version and project asset version
25
29
  help Show this help message
26
30
  `);
27
31
  }
@@ -89,6 +93,56 @@ function parseUpdateArgs(argv) {
89
93
  return options;
90
94
  }
91
95
 
96
+ function parseVersionArgs(argv) {
97
+ const options = {
98
+ targetDir: process.cwd(),
99
+ globalOnly: false,
100
+ projectOnly: false,
101
+ };
102
+
103
+ for (let index = 0; index < argv.length; index += 1) {
104
+ const value = argv[index];
105
+ if (value === "--target") {
106
+ options.targetDir = path.resolve(argv[index + 1]);
107
+ index += 1;
108
+ } else if (value === "--global") {
109
+ options.globalOnly = true;
110
+ } else if (value === "--project") {
111
+ options.projectOnly = true;
112
+ } else {
113
+ throw new Error(`Unknown option: ${value}`);
114
+ }
115
+ }
116
+
117
+ if (options.globalOnly && options.projectOnly) {
118
+ throw new Error("Use either --global or --project, not both.");
119
+ }
120
+
121
+ return options;
122
+ }
123
+
124
+ function printVersion(options) {
125
+ const lines = [];
126
+ if (!options.projectOnly) {
127
+ lines.push(`cli: ${PACKAGE_VERSION}`);
128
+ }
129
+ if (!options.globalOnly) {
130
+ const projectInfo = getProjectVersionInfo({ targetDir: options.targetDir });
131
+ if (projectInfo.status === "managed") {
132
+ lines.push(`project: ${projectInfo.package_version}`);
133
+ lines.push(`platform: ${projectInfo.platform}`);
134
+ lines.push(`language: ${projectInfo.lang}`);
135
+ } else if (projectInfo.status === "legacy") {
136
+ lines.push("project: legacy");
137
+ lines.push(`platform: ${projectInfo.platform}`);
138
+ lines.push(`language: ${projectInfo.lang}`);
139
+ } else {
140
+ lines.push("project: not initialized");
141
+ }
142
+ }
143
+ console.log(lines.join("\n"));
144
+ }
145
+
92
146
  function shouldUseInteractiveInit(options) {
93
147
  if (options.lang && options.platform) {
94
148
  return false;
@@ -140,10 +194,15 @@ async function main() {
140
194
  return;
141
195
  }
142
196
 
143
- if (!["init", "install", "update"].includes(command)) {
197
+ if (!["init", "install", "update", "version"].includes(command)) {
144
198
  throw new Error(`Unknown command: ${command}`);
145
199
  }
146
200
 
201
+ if (command === "version") {
202
+ printVersion(parseVersionArgs(rest));
203
+ return;
204
+ }
205
+
147
206
  if (command === "update") {
148
207
  const options = parseUpdateArgs(rest);
149
208
  if (options.allProjects) {
@@ -162,6 +221,9 @@ async function main() {
162
221
  console.log(`superlab updated in ${options.targetDir}`);
163
222
  console.log(`platform: ${metadata.platform}`);
164
223
  console.log(`language: ${metadata.lang}`);
224
+ if (metadata.migration) {
225
+ console.log(`migration: ${metadata.migration}`);
226
+ }
165
227
  return;
166
228
  }
167
229
 
@@ -170,7 +232,7 @@ async function main() {
170
232
  if (options.platform === "all") {
171
233
  options.platform = "both";
172
234
  }
173
- installSuperlab(options);
235
+ installSuperlab({ ...options, registerProject: true });
174
236
  console.log(`superlab installed into ${options.targetDir}`);
175
237
  console.log(`platform: ${options.platform}`);
176
238
  console.log(`language: ${options.lang}`);
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.6",
4
4
  "description": "Strict /lab research workflow installer for Codex and Claude",
5
5
  "keywords": [
6
6
  "codex",