helloloop 0.2.0 → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloloop",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "HelloLoop 的 Claude Code 原生插件元数据,用于多 CLI 宿主分发。",
5
5
  "author": {
6
6
  "name": "HelloLoop"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloloop",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "面向 Codex CLI、Claude Code、Gemini CLI 的多宿主开发工作流插件,Codex 路径为首发与参考实现。",
5
5
  "author": {
6
6
  "name": "HelloLoop"
package/README.md CHANGED
@@ -112,7 +112,7 @@ pwsh -NoLogo -NoProfile -File .\scripts\install-home-plugin.ps1 -CodexHome <CODE
112
112
  安装完成后:
113
113
 
114
114
  - Codex 会写入 `<CODEX_HOME>/plugins/helloloop`
115
- - Claude 会写入 `<CLAUDE_HOME>/marketplaces/helloloop-local`
115
+ - Claude 会写入 `<CLAUDE_HOME>/plugins/marketplaces/helloloop-local`,并生成 `<CLAUDE_HOME>/plugins/cache/helloloop-local/helloloop/<VERSION>`
116
116
  - Gemini 会写入 `<GEMINI_HOME>/extensions/helloloop`
117
117
 
118
118
  如果你想在安装后做一次全宿主环境检查:
@@ -3,10 +3,12 @@
3
3
  "owner": {
4
4
  "name": "HelloLoop"
5
5
  },
6
+ "metadata": {
7
+ "description": "HelloLoop 的 Claude Code 本地 marketplace,提供基于开发文档的分析、确认与自动接续开发工作流。"
8
+ },
6
9
  "plugins": [
7
10
  {
8
11
  "name": "helloloop",
9
- "displayName": "HelloLoop",
10
12
  "description": "基于开发文档分析当前进度、展示确认单,并在确认后继续接续开发的 Claude Code 原生插件。",
11
13
  "source": "./plugins/helloloop"
12
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloloop",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "HelloLoop 的 Claude Code 原生插件。",
5
5
  "author": {
6
6
  "name": "HelloLoop"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloloop",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "HelloLoop 的 Gemini CLI 原生扩展,用于按开发文档接续推进项目开发。",
5
5
  "contextFileName": "GEMINI.md",
6
6
  "excludeTools": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloloop",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "面向 Codex CLI、Claude Code、Gemini CLI 的多宿主开发工作流插件",
5
5
  "author": "HelloLoop",
6
6
  "license": "Apache-2.0",
@@ -151,16 +151,36 @@ function collectClaudeDoctorChecks(context, options = {}) {
151
151
 
152
152
  if (options.claudeHome) {
153
153
  const settingsFile = path.join(options.claudeHome, "settings.json");
154
+ const knownMarketplacesFile = path.join(options.claudeHome, "plugins", "known_marketplaces.json");
155
+ const installedPluginsFile = path.join(options.claudeHome, "plugins", "installed_plugins.json");
154
156
  const settings = fileExists(settingsFile) ? readJson(settingsFile) : {};
157
+ const installedPlugins = fileExists(installedPluginsFile) ? readJson(installedPluginsFile) : {};
158
+ const installs = Array.isArray(installedPlugins?.plugins?.["helloloop@helloloop-local"])
159
+ ? installedPlugins.plugins["helloloop@helloloop-local"]
160
+ : [];
161
+ const installedPluginRoot = installs[0]?.installPath
162
+ ? String(installs[0].installPath)
163
+ : path.join(options.claudeHome, "plugins", "cache", "helloloop-local", "helloloop");
164
+
155
165
  checks.push({
156
166
  name: "claude installed marketplace",
157
- ok: fileExists(path.join(options.claudeHome, "marketplaces", "helloloop-local", ".claude-plugin", "marketplace.json")),
158
- detail: path.join(options.claudeHome, "marketplaces", "helloloop-local", ".claude-plugin", "marketplace.json"),
167
+ ok: fileExists(path.join(options.claudeHome, "plugins", "marketplaces", "helloloop-local", ".claude-plugin", "marketplace.json")),
168
+ detail: path.join(options.claudeHome, "plugins", "marketplaces", "helloloop-local", ".claude-plugin", "marketplace.json"),
169
+ });
170
+ checks.push({
171
+ name: "claude marketplace registry",
172
+ ok: fileExists(knownMarketplacesFile),
173
+ detail: knownMarketplacesFile,
174
+ });
175
+ checks.push({
176
+ name: "claude installed plugin index",
177
+ ok: fileExists(installedPluginsFile),
178
+ detail: installedPluginsFile,
159
179
  });
160
180
  checks.push({
161
181
  name: "claude installed plugin",
162
- ok: fileExists(path.join(options.claudeHome, "marketplaces", "helloloop-local", "plugins", "helloloop", ".claude-plugin", "plugin.json")),
163
- detail: path.join(options.claudeHome, "marketplaces", "helloloop-local", "plugins", "helloloop", ".claude-plugin", "plugin.json"),
182
+ ok: fileExists(path.join(installedPluginRoot, ".claude-plugin", "plugin.json")),
183
+ detail: path.join(installedPluginRoot, ".claude-plugin", "plugin.json"),
164
184
  });
165
185
  checks.push({
166
186
  name: "claude settings enabled",
package/src/install.mjs CHANGED
@@ -3,7 +3,7 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
- import { ensureDir, fileExists, readJson, writeJson } from "./common.mjs";
6
+ import { ensureDir, fileExists, nowIso, readJson, writeJson } from "./common.mjs";
7
7
 
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = path.dirname(__filename);
@@ -28,6 +28,8 @@ const codexBundleEntries = runtimeBundleEntries.filter((entry) => ![
28
28
  ].includes(entry));
29
29
 
30
30
  const supportedHosts = ["codex", "claude", "gemini"];
31
+ const CLAUDE_MARKETPLACE_NAME = "helloloop-local";
32
+ const CLAUDE_PLUGIN_KEY = "helloloop@helloloop-local";
31
33
 
32
34
  function resolveHomeDir(homeDir, defaultDirName) {
33
35
  return path.resolve(homeDir || path.join(os.homedir(), defaultDirName));
@@ -118,15 +120,57 @@ function updateClaudeSettings(settingsFile, marketplaceRoot) {
118
120
  settings.extraKnownMarketplaces = settings.extraKnownMarketplaces || {};
119
121
  settings.enabledPlugins = settings.enabledPlugins || {};
120
122
 
121
- settings.extraKnownMarketplaces["helloloop-local"] = {
122
- source: "directory",
123
- path: marketplaceRoot.replaceAll("\\", "/"),
123
+ settings.extraKnownMarketplaces[CLAUDE_MARKETPLACE_NAME] = {
124
+ source: {
125
+ source: "directory",
126
+ path: marketplaceRoot,
127
+ },
124
128
  };
125
- settings.enabledPlugins["helloloop@helloloop-local"] = true;
129
+ settings.enabledPlugins[CLAUDE_PLUGIN_KEY] = true;
126
130
 
127
131
  writeJson(settingsFile, settings);
128
132
  }
129
133
 
134
+ function updateClaudeKnownMarketplaces(knownMarketplacesFile, marketplaceRoot, updatedAt) {
135
+ const knownMarketplaces = fileExists(knownMarketplacesFile)
136
+ ? readJson(knownMarketplacesFile)
137
+ : {};
138
+
139
+ knownMarketplaces[CLAUDE_MARKETPLACE_NAME] = {
140
+ source: {
141
+ source: "directory",
142
+ path: marketplaceRoot,
143
+ },
144
+ installLocation: marketplaceRoot,
145
+ lastUpdated: updatedAt,
146
+ };
147
+
148
+ writeJson(knownMarketplacesFile, knownMarketplaces);
149
+ }
150
+
151
+ function updateClaudeInstalledPlugins(installedPluginsFile, pluginRoot, pluginVersion, updatedAt) {
152
+ const installedPlugins = fileExists(installedPluginsFile)
153
+ ? readJson(installedPluginsFile)
154
+ : {
155
+ version: 2,
156
+ plugins: {},
157
+ };
158
+
159
+ installedPlugins.version = 2;
160
+ installedPlugins.plugins = installedPlugins.plugins || {};
161
+ installedPlugins.plugins[CLAUDE_PLUGIN_KEY] = [
162
+ {
163
+ scope: "user",
164
+ installPath: pluginRoot,
165
+ version: pluginVersion,
166
+ installedAt: updatedAt,
167
+ lastUpdated: updatedAt,
168
+ },
169
+ ];
170
+
171
+ writeJson(installedPluginsFile, installedPlugins);
172
+ }
173
+
130
174
  function installCodexHost(bundleRoot, options) {
131
175
  const resolvedCodexHome = resolveHomeDir(options.codexHome, ".codex");
132
176
  const targetPluginsRoot = path.join(resolvedCodexHome, "plugins");
@@ -165,7 +209,13 @@ function installClaudeHost(bundleRoot, options) {
165
209
  const resolvedClaudeHome = resolveHomeDir(options.claudeHome, ".claude");
166
210
  const sourceMarketplaceRoot = path.join(bundleRoot, "hosts", "claude", "marketplace");
167
211
  const sourceManifest = path.join(bundleRoot, ".claude-plugin", "plugin.json");
168
- const targetMarketplaceRoot = path.join(resolvedClaudeHome, "marketplaces", "helloloop-local");
212
+ const pluginVersion = readJson(sourceManifest).version || readJson(path.join(bundleRoot, "package.json")).version;
213
+ const targetPluginsRoot = path.join(resolvedClaudeHome, "plugins");
214
+ const targetMarketplaceRoot = path.join(targetPluginsRoot, "marketplaces", CLAUDE_MARKETPLACE_NAME);
215
+ const targetCachePluginsRoot = path.join(targetPluginsRoot, "cache", CLAUDE_MARKETPLACE_NAME, "helloloop");
216
+ const targetInstalledPluginRoot = path.join(targetCachePluginsRoot, pluginVersion);
217
+ const knownMarketplacesFile = path.join(targetPluginsRoot, "known_marketplaces.json");
218
+ const installedPluginsFile = path.join(targetPluginsRoot, "installed_plugins.json");
169
219
  const settingsFile = path.join(resolvedClaudeHome, "settings.json");
170
220
 
171
221
  if (!fileExists(sourceManifest)) {
@@ -176,16 +226,24 @@ function installClaudeHost(bundleRoot, options) {
176
226
  }
177
227
 
178
228
  assertPathInside(resolvedClaudeHome, targetMarketplaceRoot, "Claude marketplace 目录");
229
+ assertPathInside(resolvedClaudeHome, targetInstalledPluginRoot, "Claude 插件缓存目录");
179
230
  removeTargetIfNeeded(targetMarketplaceRoot, options.force);
231
+ removeTargetIfNeeded(targetCachePluginsRoot, options.force);
180
232
 
181
233
  ensureDir(path.dirname(targetMarketplaceRoot));
234
+ ensureDir(path.dirname(targetInstalledPluginRoot));
182
235
  copyDirectory(sourceMarketplaceRoot, targetMarketplaceRoot);
236
+ copyDirectory(path.join(sourceMarketplaceRoot, "plugins", "helloloop"), targetInstalledPluginRoot);
237
+ ensureDir(targetPluginsRoot);
238
+ const updatedAt = nowIso();
183
239
  updateClaudeSettings(settingsFile, targetMarketplaceRoot);
240
+ updateClaudeKnownMarketplaces(knownMarketplacesFile, targetMarketplaceRoot, updatedAt);
241
+ updateClaudeInstalledPlugins(installedPluginsFile, targetInstalledPluginRoot, pluginVersion, updatedAt);
184
242
 
185
243
  return {
186
244
  host: "claude",
187
245
  displayName: "Claude",
188
- targetRoot: path.join(targetMarketplaceRoot, "plugins", "helloloop"),
246
+ targetRoot: targetInstalledPluginRoot,
189
247
  marketplaceFile: path.join(targetMarketplaceRoot, ".claude-plugin", "marketplace.json"),
190
248
  settingsFile,
191
249
  };