superlab 0.1.4 → 0.1.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/README.md CHANGED
@@ -68,6 +68,28 @@ superlab init --all
68
68
 
69
69
  `--all` is an alias for `--platform both`. Use `--force` if you need to overwrite existing files.
70
70
 
71
+ ## Update
72
+
73
+ Refresh the current project:
74
+
75
+ ```bash
76
+ superlab update
77
+ ```
78
+
79
+ Refresh a specific project:
80
+
81
+ ```bash
82
+ superlab update --target /path/to/project
83
+ ```
84
+
85
+ Refresh every registered project initialized from this user account:
86
+
87
+ ```bash
88
+ superlab update --all-projects
89
+ ```
90
+
91
+ `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
+
71
93
  ## Language
72
94
 
73
95
  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
@@ -66,6 +66,28 @@ superlab init --all
66
66
 
67
67
  `--all` 是 `--platform both` 的别名。如果目标目录已有同名文件,可以加 `--force` 覆盖。
68
68
 
69
+ ## 更新
70
+
71
+ 刷新当前项目:
72
+
73
+ ```bash
74
+ superlab update
75
+ ```
76
+
77
+ 刷新指定项目:
78
+
79
+ ```bash
80
+ superlab update --target /path/to/project
81
+ ```
82
+
83
+ 刷新当前用户登记过的所有项目:
84
+
85
+ ```bash
86
+ superlab update --all-projects
87
+ ```
88
+
89
+ `superlab init` 会在项目内写入 `.superlab/install.json`,并在用户级 registry 里登记项目路径,所以 `update --all-projects` 才知道要刷新哪些项目。
90
+
69
91
  ## 语言
70
92
 
71
93
  安装器会根据系统 locale 自动猜测展示语言。检测到中文 locale 时会安装中文命令和技能文案;否则默认安装英文文案。
package/bin/superlab.cjs CHANGED
@@ -1,7 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const path = require("node:path");
4
- const { detectLanguage, installSuperlab } = require("../lib/install.cjs");
4
+ const {
5
+ detectLanguage,
6
+ installSuperlab,
7
+ updateAllProjects,
8
+ updateSuperlabProject,
9
+ } = require("../lib/install.cjs");
5
10
  const { promptSelect } = require("../lib/init_tui.cjs");
6
11
 
7
12
  function printHelp() {
@@ -10,10 +15,13 @@ function printHelp() {
10
15
  Usage:
11
16
  superlab init [--target <dir>] [--platform codex|claude|both|all] [--lang en|zh] [--force]
12
17
  superlab install [--target <dir>] [--platform codex|claude|both|all] [--lang en|zh] [--force]
18
+ superlab update [--target <dir>]
19
+ superlab update --all-projects
13
20
 
14
21
  Commands:
15
22
  init Initialize /lab commands, skills, templates, and scripts in a target
16
23
  install Backward-compatible alias for init
24
+ update Refresh an initialized project or all registered projects
17
25
  help Show this help message
18
26
  `);
19
27
  }
@@ -56,6 +64,31 @@ function parseInstallArgs(argv) {
56
64
  return options;
57
65
  }
58
66
 
67
+ function parseUpdateArgs(argv) {
68
+ const options = {
69
+ targetDir: process.cwd(),
70
+ allProjects: false,
71
+ };
72
+
73
+ for (let index = 0; index < argv.length; index += 1) {
74
+ const value = argv[index];
75
+ if (value === "--target") {
76
+ options.targetDir = path.resolve(argv[index + 1]);
77
+ index += 1;
78
+ } else if (value === "--all-projects") {
79
+ options.allProjects = true;
80
+ } else {
81
+ throw new Error(`Unknown option: ${value}`);
82
+ }
83
+ }
84
+
85
+ if (options.allProjects && argv.includes("--target")) {
86
+ throw new Error("Use either --target or --all-projects, not both.");
87
+ }
88
+
89
+ return options;
90
+ }
91
+
59
92
  function shouldUseInteractiveInit(options) {
60
93
  if (options.lang && options.platform) {
61
94
  return false;
@@ -107,10 +140,31 @@ async function main() {
107
140
  return;
108
141
  }
109
142
 
110
- if (!["init", "install"].includes(command)) {
143
+ if (!["init", "install", "update"].includes(command)) {
111
144
  throw new Error(`Unknown command: ${command}`);
112
145
  }
113
146
 
147
+ if (command === "update") {
148
+ const options = parseUpdateArgs(rest);
149
+ if (options.allProjects) {
150
+ const result = updateAllProjects();
151
+ console.log(`updated projects: ${result.updated.length}`);
152
+ for (const project of result.updated) {
153
+ console.log(`updated: ${project}`);
154
+ }
155
+ for (const project of result.skipped) {
156
+ console.log(`skipped: ${project.path} (${project.reason})`);
157
+ }
158
+ return;
159
+ }
160
+
161
+ const metadata = updateSuperlabProject({ targetDir: options.targetDir });
162
+ console.log(`superlab updated in ${options.targetDir}`);
163
+ console.log(`platform: ${metadata.platform}`);
164
+ console.log(`language: ${metadata.lang}`);
165
+ return;
166
+ }
167
+
114
168
  const parsedOptions = parseInstallArgs(rest);
115
169
  const options = await resolveInitOptions(parsedOptions);
116
170
  if (options.platform === "all") {
package/lib/install.cjs CHANGED
@@ -1,8 +1,10 @@
1
1
  const fs = require("node:fs");
2
+ const os = require("node:os");
2
3
  const path = require("node:path");
3
4
  const { getLocalizedContent } = require("./i18n.cjs");
4
5
 
5
6
  const REPO_ROOT = path.resolve(__dirname, "..");
7
+ const PACKAGE_VERSION = require(path.join(REPO_ROOT, "package.json")).version;
6
8
 
7
9
  const ASSET_GROUPS = {
8
10
  codex: [
@@ -102,6 +104,72 @@ function chmodScripts(targetDir) {
102
104
  }
103
105
  }
104
106
 
107
+ function superlabHomeDir({ env = process.env } = {}) {
108
+ if (env.SUPERLAB_HOME) {
109
+ return env.SUPERLAB_HOME;
110
+ }
111
+ return path.join(env.HOME || os.homedir(), ".superlab");
112
+ }
113
+
114
+ function registryFilePath({ env = process.env } = {}) {
115
+ return path.join(superlabHomeDir({ env }), "registry.json");
116
+ }
117
+
118
+ function installMetadataPath(targetDir) {
119
+ return path.join(targetDir, ".superlab", "install.json");
120
+ }
121
+
122
+ function ensureParentDir(filePath) {
123
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
124
+ }
125
+
126
+ function writeProjectInstallMetadata(targetDir, metadata) {
127
+ const filePath = installMetadataPath(targetDir);
128
+ ensureParentDir(filePath);
129
+ fs.writeFileSync(filePath, JSON.stringify(metadata, null, 2) + "\n");
130
+ }
131
+
132
+ function readProjectInstallMetadata(targetDir) {
133
+ const filePath = installMetadataPath(targetDir);
134
+ if (!fs.existsSync(filePath)) {
135
+ throw new Error(`No superlab installation metadata found in ${targetDir}. Run 'superlab init' first.`);
136
+ }
137
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
138
+ }
139
+
140
+ function readRegistry({ env = process.env } = {}) {
141
+ const filePath = registryFilePath({ env });
142
+ if (!fs.existsSync(filePath)) {
143
+ return { projects: [] };
144
+ }
145
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
146
+ }
147
+
148
+ function writeRegistry(registry, { env = process.env } = {}) {
149
+ const filePath = registryFilePath({ env });
150
+ ensureParentDir(filePath);
151
+ fs.writeFileSync(filePath, JSON.stringify(registry, null, 2) + "\n");
152
+ }
153
+
154
+ function registerProjectInstall(targetDir, metadata, { env = process.env } = {}) {
155
+ const registry = readRegistry({ env });
156
+ const normalizedTarget = path.resolve(targetDir);
157
+ const entry = {
158
+ path: normalizedTarget,
159
+ platform: metadata.platform,
160
+ lang: metadata.lang,
161
+ package_version: metadata.package_version,
162
+ updated_at: metadata.updated_at,
163
+ };
164
+ const existingIndex = registry.projects.findIndex((project) => project.path === normalizedTarget);
165
+ if (existingIndex >= 0) {
166
+ registry.projects[existingIndex] = entry;
167
+ } else {
168
+ registry.projects.push(entry);
169
+ }
170
+ writeRegistry(registry, { env });
171
+ }
172
+
105
173
  function detectLanguage({ explicitLang, env = process.env } = {}) {
106
174
  if (explicitLang) {
107
175
  if (!["en", "zh"].includes(explicitLang)) {
@@ -175,7 +243,7 @@ function localizeInstalledAssets(targetDir, lang) {
175
243
  }
176
244
  }
177
245
 
178
- function installSuperlab({ targetDir, platform = "both", force = false, lang } = {}) {
246
+ function installSuperlab({ targetDir, platform = "both", force = false, lang, env = process.env } = {}) {
179
247
  const groups = [];
180
248
  if (platform === "codex" || platform === "both") {
181
249
  groups.push(...ASSET_GROUPS.codex);
@@ -188,11 +256,60 @@ function installSuperlab({ targetDir, platform = "both", force = false, lang } =
188
256
  for (const asset of groups) {
189
257
  copyDirectory(asset.from, path.join(targetDir, asset.to), force);
190
258
  }
191
- localizeInstalledAssets(targetDir, detectLanguage({ explicitLang: lang }));
259
+ const resolvedLang = detectLanguage({ explicitLang: lang, env });
260
+ localizeInstalledAssets(targetDir, resolvedLang);
192
261
  chmodScripts(targetDir);
262
+ const metadata = {
263
+ target_dir: path.resolve(targetDir),
264
+ platform,
265
+ lang: resolvedLang,
266
+ package_version: PACKAGE_VERSION,
267
+ updated_at: new Date().toISOString(),
268
+ };
269
+ writeProjectInstallMetadata(targetDir, metadata);
270
+ registerProjectInstall(targetDir, metadata, { env });
271
+ }
272
+
273
+ function updateSuperlabProject({ targetDir, env = process.env } = {}) {
274
+ const metadata = readProjectInstallMetadata(targetDir);
275
+ installSuperlab({
276
+ targetDir,
277
+ platform: metadata.platform,
278
+ lang: metadata.lang,
279
+ force: true,
280
+ env,
281
+ });
282
+ return metadata;
283
+ }
284
+
285
+ function updateAllProjects({ env = process.env } = {}) {
286
+ const registry = readRegistry({ env });
287
+ const updated = [];
288
+ const skipped = [];
289
+
290
+ for (const project of registry.projects) {
291
+ try {
292
+ if (!fs.existsSync(project.path)) {
293
+ skipped.push({ path: project.path, reason: "missing project path" });
294
+ continue;
295
+ }
296
+ updateSuperlabProject({ targetDir: project.path, env });
297
+ updated.push(project.path);
298
+ } catch (error) {
299
+ skipped.push({ path: project.path, reason: error.message });
300
+ }
301
+ }
302
+
303
+ return { updated, skipped };
193
304
  }
194
305
 
195
306
  module.exports = {
196
307
  detectLanguage,
197
308
  installSuperlab,
309
+ installMetadataPath,
310
+ readProjectInstallMetadata,
311
+ registryFilePath,
312
+ readRegistry,
313
+ updateAllProjects,
314
+ updateSuperlabProject,
198
315
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlab",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Strict /lab research workflow installer for Codex and Claude",
5
5
  "keywords": [
6
6
  "codex",