gipity 1.0.389 → 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';
@@ -513,7 +513,9 @@ export const claudeCommand = new Command('claude')
513
513
  // .gipity.json into cwd (no projects-root materialization).
514
514
  const cwdBase = basename(process.cwd());
515
515
  const adoptSlug = slugify(cwdBase) || 'project';
516
- const adoptName = cwdBase || adoptSlug;
516
+ // Server caps name at 100 chars; clamp so a very long dir name
517
+ // doesn't trip a "Too big" 400 (slugify already clamps the slug).
518
+ const adoptName = (cwdBase || adoptSlug).slice(0, 100);
517
519
  accountSlug = await getAccountSlug();
518
520
  const adopted = await adoptCurrentDir({
519
521
  cwd: process.cwd(),
@@ -599,6 +601,10 @@ export const claudeCommand = new Command('claude')
599
601
  console.log(` Then: cd ${process.cwd()} && claude`);
600
602
  return;
601
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();
602
608
  // Resolve (or create) the backing Gipity conversation for this
603
609
  // Claude Code run. The conv_guid is handed to the child (and thus
604
610
  // every capture hook spawned by Claude Code) via env var so every
@@ -112,8 +112,9 @@ Working with an existing Gipity project:
112
112
  process.exit(1);
113
113
  }
114
114
  }
115
- // Resolve project name
116
- const projectName = name || basename(cwd);
115
+ // Resolve project name. Server caps name at 100 chars; clamp so a very
116
+ // long dir name doesn't trip a "Too big" 400 (slugify clamps the slug).
117
+ const projectName = (name || basename(cwd)).slice(0, 100);
117
118
  const projectSlug = slugify(projectName);
118
119
  if (!projectSlug) {
119
120
  console.error(clrError('Could not derive a valid project slug. Provide a name: gipity init my-app'));
@@ -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).
@@ -404,11 +489,23 @@ export function setupGitignore() {
404
489
  writeFileSync(gitignorePath, entries.join('\n') + '\n');
405
490
  }
406
491
  }
492
+ /** The server caps slugs at 50 (MAX_PROJECT_SLUG_LENGTH); we cap shorter for
493
+ * readability, since the slug is also the on-disk folder and the URL path
494
+ * segment. Keeps long-named directories from producing valid-but-ugly slugs. */
495
+ export const MAX_SLUG_LENGTH = 40;
407
496
  export function slugify(name) {
408
- return name
497
+ const slug = name
409
498
  .toLowerCase()
410
499
  .replace(/[^a-z0-9-]/g, '-')
411
500
  .replace(/-+/g, '-')
412
501
  .replace(/^-|-$/g, '');
502
+ if (slug.length <= MAX_SLUG_LENGTH)
503
+ return slug;
504
+ // Cut at the last word (hyphen) boundary within the cap so we don't slice
505
+ // mid-word (e.g. "...call-no"). Fall back to a hard cut if the first word
506
+ // alone already exceeds the cap.
507
+ const cut = slug.slice(0, MAX_SLUG_LENGTH);
508
+ const lastHyphen = cut.lastIndexOf('-');
509
+ return (lastHyphen > 0 ? cut.slice(0, lastHyphen) : cut).replace(/-+$/, '');
413
510
  }
414
511
  //# sourceMappingURL=setup.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gipity",
3
- "version": "1.0.389",
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",