gipity 1.0.390 → 1.0.391

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.
@@ -21,7 +21,7 @@ import { getAuth, saveAuth, clearAuth } from '../auth.js';
21
21
  import { get, post, publicPost, ApiError, getAccountSlug } from '../api.js';
22
22
  import { getConfig, saveConfigAt, clearConfigCache, getApiBaseOverride, DEFAULT_API_BASE, getConfigPath } from '../config.js';
23
23
  import { sync } from '../sync.js';
24
- import { slugify, setupClaudeHooks, setupClaudeMd, setupAgentsMd, setupGitignore, DEFAULT_SYNC_IGNORE, isSyncIgnored } from '../setup.js';
24
+ import { slugify, setupClaudeHooks, ensureGipityPluginInstalled, setupClaudeMd, setupAgentsMd, setupGitignore, DEFAULT_SYNC_IGNORE, isSyncIgnored } from '../setup.js';
25
25
  import { buildProjectContextBlock as buildProjectContextBlockText, buildNewProjectPrompt, buildResumeWrap, buildFreshWrap, } from '../prompts.js';
26
26
  import * as relayState from '../relay/state.js';
27
27
  import { maybeOfferRelayOn, ensureDaemonRunning } from '../relay/onboarding.js';
@@ -601,6 +601,10 @@ export const claudeCommand = new Command('claude')
601
601
  console.log(` Then: cd ${process.cwd()} && claude`);
602
602
  return;
603
603
  }
604
+ // Ensure the Gipity plugin is actually installed at user scope (not just
605
+ // enabled declaratively) so its capture + file-sync hooks load in this
606
+ // run's cwd. No-ops once the current version is installed; best-effort.
607
+ ensureGipityPluginInstalled();
604
608
  // Resolve (or create) the backing Gipity conversation for this
605
609
  // Claude Code run. The conv_guid is handed to the child (and thus
606
610
  // every capture hook spawned by Claude Code) via env var so every
@@ -5,10 +5,14 @@ import { homedir } from 'os';
5
5
  import { getAuth, sessionExpired } from '../auth.js';
6
6
  import { getConfig, liveUrl } from '../config.js';
7
7
  import { brand, success, warning, muted, error as clrError } from '../colors.js';
8
- import { GIPITY_PLUGIN_ID, GIPITY_MARKETPLACE_NAME, setupClaudeHooks, ensureGipityPlugin } from '../setup.js';
9
- /** Hooks ship in the Gipity Claude Code plugin now - "installed" means the
10
- * user-scope settings register the marketplace and enable the plugin.
11
- * Claude Code fetches/updates the plugin itself at launch. */
8
+ import { GIPITY_PLUGIN_ID, GIPITY_MARKETPLACE_NAME, setupClaudeHooks, ensureGipityPlugin, ensureGipityPluginInstalled, userScopePluginCurrent } from '../setup.js';
9
+ /** Hooks ship in the Gipity Claude Code plugin now. "Installed" means three
10
+ * things must all hold: the user-scope settings register the marketplace and
11
+ * enable the plugin (declarative), AND Claude Code actually has a user-scope
12
+ * install of the current version on disk. The last check matters because
13
+ * CC >=2.1.x does not materialize a user-scope install from enablement alone -
14
+ * without it the hooks never load and capture/file-sync silently die, so
15
+ * reporting "ok" on the declarative keys alone would be a false green. */
12
16
  function checkGipityPlugin() {
13
17
  const path = join(homedir(), '.claude', 'settings.json');
14
18
  let settings = {};
@@ -23,6 +27,8 @@ function checkGipityPlugin() {
23
27
  missing.push('marketplace');
24
28
  if (settings?.enabledPlugins?.[GIPITY_PLUGIN_ID] !== true)
25
29
  missing.push('plugin');
30
+ if (!userScopePluginCurrent())
31
+ missing.push('install');
26
32
  return { missing, ok: missing.length === 0 };
27
33
  }
28
34
  export const statusCommand = new Command('status')
@@ -82,6 +88,9 @@ export const statusCommand = new Command('status')
82
88
  // force: an explicit repair request overrides a previous disable.
83
89
  ensureGipityPlugin(true);
84
90
  setupClaudeHooks();
91
+ // Re-enabling the declarative keys isn't enough on CC >=2.1.x - also
92
+ // materialize the user-scope install so the hooks actually load.
93
+ ensureGipityPluginInstalled();
85
94
  console.log(`${muted('Hooks:')} ${success('repaired - Gipity plugin re-enabled')}`);
86
95
  }
87
96
  else {
package/dist/setup.js CHANGED
@@ -4,6 +4,7 @@
4
4
  import { resolve, join, dirname } from 'path';
5
5
  import { homedir } from 'os';
6
6
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
7
+ import { spawnSync } from 'child_process';
7
8
  import { SKILLS_CONTENT, BUILD_VS_NON_BUILD_RULE, DEFINITION_OF_DONE } from './knowledge.js';
8
9
  export { SKILLS_CONTENT };
9
10
  /** Canonical list of workstation artifacts that are NOT part of the project.
@@ -100,13 +101,27 @@ export const PERMISSIONS_SETTINGS = {
100
101
  // into each project's .claude/settings.json with absolute paths baked in -
101
102
  // that left orphaned entries behind on uninstall (the CLI keeps no inventory
102
103
  // of projects it touched) and could even land in the user-global settings
103
- // when a gipity command ran from $HOME. The plugin replaces all of it with
104
- // one declarative enablement entry: Claude Code resolves script paths via
105
- // ${CLAUDE_PLUGIN_ROOT}, fetches the marketplace non-interactively at launch
106
- // (headless included), and uninstall/disable removes every hook at once.
104
+ // when a gipity command ran from $HOME. The plugin replaces all of it: Claude
105
+ // Code resolves script paths via ${CLAUDE_PLUGIN_ROOT} and uninstall/disable
106
+ // removes every hook at once.
107
+ //
108
+ // Two steps are needed to make it load, split by testability and cost:
109
+ // - ensureGipityPlugin() - declarative: register the marketplace +
110
+ // enable the plugin in ~/.claude/settings.json. Pure file writes.
111
+ // - ensureGipityPluginInstalled() - imperative: actually install the plugin
112
+ // at USER scope via the `claude plugin` CLI. Required because CC >=2.1.x no
113
+ // longer materializes a user-scope install from enablement alone; without
114
+ // it the hooks load only inside whatever project happened to install the
115
+ // plugin (often nowhere), silently taking capture + file-sync down.
107
116
  export const GIPITY_PLUGIN_ID = 'gipity@gipity';
108
117
  export const GIPITY_MARKETPLACE_NAME = 'gipity';
109
118
  export const GIPITY_MARKETPLACE_REPO = 'GipityAI/claude-plugin';
119
+ // The plugin version this CLI requires. Bump in lockstep with
120
+ // claude-plugin/.claude-plugin/plugin.json: Claude Code does NOT auto-upgrade
121
+ // an installed plugin when the marketplace advances - only an explicit
122
+ // `plugin install`/`update` does - so this constant is how a CLI upgrade tells
123
+ // ensureGipityPluginInstalled() to refresh a stale user-scope install.
124
+ export const GIPITY_PLUGIN_VERSION = '0.4.0';
110
125
  /** True for hook commands the CLI itself wrote into settings.json in past
111
126
  * versions. Matched by signature so migration strips exactly our own
112
127
  * entries and never touches user-authored hooks. */
@@ -192,6 +207,76 @@ export function ensureGipityPlugin(force = false) {
192
207
  mkdirSync(claudeDir, { recursive: true });
193
208
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
194
209
  }
210
+ /** Dotted-numeric version compare: true when `have` >= `want` (e.g. "0.4.0"). */
211
+ function versionGte(have, want) {
212
+ const h = have.split('.').map((n) => parseInt(n, 10) || 0);
213
+ const w = want.split('.').map((n) => parseInt(n, 10) || 0);
214
+ for (let i = 0; i < Math.max(h.length, w.length); i++) {
215
+ const a = h[i] ?? 0;
216
+ const b = w[i] ?? 0;
217
+ if (a !== b)
218
+ return a > b;
219
+ }
220
+ return true;
221
+ }
222
+ /** True when Claude Code already records a USER-scope install of the Gipity
223
+ * plugin at >= the version this CLI needs - the common case, letting the
224
+ * caller skip the (slow) reinstall. Reads installed_plugins.json directly so
225
+ * the check costs no subprocess. Exported so `gipity status` can tell an
226
+ * actually-loaded plugin apart from one that's merely enabled-but-uninstalled
227
+ * (which would otherwise read as a false-green "hooks enabled"). */
228
+ export function userScopePluginCurrent() {
229
+ try {
230
+ const p = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');
231
+ const data = JSON.parse(readFileSync(p, 'utf-8'));
232
+ const entries = data?.plugins?.[GIPITY_PLUGIN_ID];
233
+ if (!Array.isArray(entries))
234
+ return false;
235
+ return entries.some((e) => e?.scope === 'user' &&
236
+ typeof e?.version === 'string' &&
237
+ versionGte(e.version, GIPITY_PLUGIN_VERSION));
238
+ }
239
+ catch {
240
+ return false;
241
+ }
242
+ }
243
+ function claudeOnPath() {
244
+ const probe = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['claude'], {
245
+ encoding: 'utf-8',
246
+ });
247
+ return probe.status === 0 && !!probe.stdout?.trim();
248
+ }
249
+ /** Materialize the Gipity plugin at USER scope via Claude Code's own plugin
250
+ * CLI, so its hooks (session capture + file sync) load in EVERY directory.
251
+ *
252
+ * ensureGipityPlugin() only writes the declarative `enabledPlugins` /
253
+ * `extraKnownMarketplaces` keys. That was enough on older Claude Code, but
254
+ * CC >=2.1.x no longer materializes a user-scope install from an enablement
255
+ * entry alone: without an actual user-scope install the plugin loads only in
256
+ * whatever project happened to install it (often nowhere), so capture and
257
+ * file-sync silently go dark everywhere else. We drive the supported
258
+ * `claude plugin` commands rather than trust implicit resolution.
259
+ *
260
+ * Best-effort and non-fatal - a missing `claude` or a failed install must
261
+ * never break `gipity claude`. Skips entirely when the user-scope install is
262
+ * already current, so it shells out at most once per plugin-version bump. */
263
+ export function ensureGipityPluginInstalled() {
264
+ if (userScopePluginCurrent())
265
+ return;
266
+ if (!claudeOnPath())
267
+ return;
268
+ // Refresh the marketplace clone so `install` resolves the current version,
269
+ // then (re)install at user scope - idempotent, and upgrades an older or
270
+ // project-scoped install to the current one at user scope.
271
+ spawnSync('claude', ['plugin', 'marketplace', 'update', GIPITY_MARKETPLACE_NAME], {
272
+ stdio: 'ignore',
273
+ timeout: 120_000,
274
+ });
275
+ spawnSync('claude', ['plugin', 'install', GIPITY_PLUGIN_ID, '--scope', 'user'], {
276
+ stdio: 'ignore',
277
+ timeout: 120_000,
278
+ });
279
+ }
195
280
  export function setupClaudeHooks() {
196
281
  // All hooks ship in the plugin - enable it at user scope (and clean up any
197
282
  // legacy hook blocks in the user-global settings while we're there).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gipity",
3
- "version": "1.0.390",
3
+ "version": "1.0.391",
4
4
  "description": "The full-stack platform tuned for AI agents. Database, storage, auth, functions, deploy, and drop-in kits - all agent-tuned. Pair with Claude Code or use standalone.",
5
5
  "bin": {
6
6
  "gipity": "dist/updater/shim.js",