gipity 1.0.413 → 1.0.416

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.
@@ -11,7 +11,7 @@
11
11
  * and no `process.exit` in this module: callers own all UX.
12
12
  */
13
13
  import { spawnCommand, spawnSyncCommand } from '../platform.js';
14
- import { hostname, platform as osPlatform } from 'os';
14
+ import { hostname, userInfo, platform as osPlatform } from 'os';
15
15
  import { resolve, dirname } from 'path';
16
16
  import { mkdirSync, writeFileSync } from 'fs';
17
17
  import { post } from '../api.js';
@@ -24,6 +24,33 @@ export function mapPlatform(p) {
24
24
  return p;
25
25
  return 'linux';
26
26
  }
27
+ /**
28
+ * A friendly default device name for the web CLI — e.g. "Steve's Mac",
29
+ * "Steve's Windows PC". Prefers the OS login name + device kind, which reads
30
+ * better and is more recognizable than a raw hostname like "SignalOrangeXps".
31
+ * Falls back to the hostname, then a generic label. The user can override at
32
+ * the prompt, and the server de-dupes collisions ("Steve's Mac-2").
33
+ */
34
+ export function friendlyDeviceName() {
35
+ const kind = osPlatform() === 'darwin' ? 'Mac' :
36
+ osPlatform() === 'win32' ? 'Windows PC' :
37
+ 'Linux PC';
38
+ let user = '';
39
+ try {
40
+ user = (userInfo().username || '').trim();
41
+ }
42
+ catch {
43
+ // userInfo() throws when there's no matching passwd entry (some containers).
44
+ }
45
+ // Generic/service accounts carry no signal — the hostname is more useful there.
46
+ const generic = new Set(['root', 'user', 'admin', 'ubuntu', 'ec2-user', 'administrator', 'pi']);
47
+ if (user && !generic.has(user.toLowerCase())) {
48
+ const owner = user.charAt(0).toUpperCase() + user.slice(1);
49
+ return `${owner}'s ${kind}`;
50
+ }
51
+ const host = (hostname() || '').trim();
52
+ return host || `My ${kind}`;
53
+ }
27
54
  /** Absolute path to the currently-running `gipity` CLI. Embedded in service
28
55
  * units so launchd/systemd/Task Scheduler re-launch the same binary even if
29
56
  * PATH changes. */
@@ -59,7 +86,7 @@ export async function pairDevice(opts = {}) {
59
86
  }
60
87
  state.clearDevice();
61
88
  }
62
- const name = (opts.name?.trim() || hostname() || 'my-pc').trim();
89
+ const name = (opts.name?.trim() || friendlyDeviceName()).trim();
63
90
  if (!name || name.length > 100) {
64
91
  throw new Error('Device name must be 1–100 non-whitespace characters.');
65
92
  }
@@ -38,6 +38,7 @@ export function loadState() {
38
38
  paused: Boolean(raw.paused),
39
39
  relay_enabled: typeof raw.relay_enabled === 'boolean' ? raw.relay_enabled : undefined,
40
40
  onboard_shown: Boolean(raw.onboard_shown),
41
+ diagnostics_consent: typeof raw.diagnostics_consent === 'boolean' ? raw.diagnostics_consent : undefined,
41
42
  agent_token: typeof raw.agent_token === 'string' ? raw.agent_token : null,
42
43
  agent_token_guid: typeof raw.agent_token_guid === 'string' ? raw.agent_token_guid : null,
43
44
  };
@@ -113,6 +114,23 @@ export function isRelayEnabled() {
113
114
  export function setRelayEnabled(enabled) {
114
115
  mutate(s => { s.relay_enabled = enabled; });
115
116
  }
117
+ // ─── Diagnostics consent (tri-state, default-on) ───────────────────────
118
+ /** Stored preference: `undefined` = never asked; `true`/`false` = explicit. */
119
+ export function getDiagnosticsConsent() {
120
+ return loadState().diagnostics_consent;
121
+ }
122
+ export function setDiagnosticsConsent(consent) {
123
+ mutate(s => { s.diagnostics_consent = consent; });
124
+ }
125
+ /** Effective consent used by the daemon: default-on unless the user explicitly
126
+ * opted out OR a headless opt-out env var (GIPITY_NO_DIAGNOSTICS / DO_NOT_TRACK)
127
+ * is set. Truthy env value ("1"/"true"/anything non-empty) disables. */
128
+ export function diagnosticsConsented() {
129
+ const env = process.env.GIPITY_NO_DIAGNOSTICS ?? process.env.DO_NOT_TRACK;
130
+ if (env && env !== '0' && env.toLowerCase() !== 'false')
131
+ return false;
132
+ return loadState().diagnostics_consent !== false;
133
+ }
116
134
  // ─── Daemon PID file (lives at ~/.gipity/relay.pid) ────────────────────
117
135
  const RELAY_PID_FILE = join(RELAY_DIR, 'relay.pid');
118
136
  export function getDaemonPidPath() {
package/dist/setup.js CHANGED
@@ -244,27 +244,39 @@ function versionGte(have, want) {
244
244
  }
245
245
  return true;
246
246
  }
247
- /** True when Claude Code already records a USER-scope install of the Gipity
248
- * plugin at >= the version this CLI needs - the common case, letting the
249
- * caller skip the (slow) reinstall. Reads installed_plugins.json directly so
250
- * the check costs no subprocess. Exported so `gipity status` can tell an
251
- * actually-loaded plugin apart from one that's merely enabled-but-uninstalled
252
- * (which would otherwise read as a false-green "hooks enabled"). */
253
- export function userScopePluginCurrent() {
247
+ /** Parsed user-scope install state for the Gipity plugin, read straight from
248
+ * installed_plugins.json (no subprocess). `exists` is true when Claude Code
249
+ * records ANY user-scope install; `current` narrows that to one at >= the
250
+ * version this CLI needs. The two differ exactly when a stale install lags a
251
+ * plugin-version bump - the case that must be UPGRADED, not freshly installed
252
+ * (a bare `claude plugin install` no-ops on an already-present user install
253
+ * and never advances its version). */
254
+ export function userScopeInstallState() {
254
255
  try {
255
256
  const p = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');
256
257
  const data = JSON.parse(readFileSync(p, 'utf-8'));
257
258
  const entries = data?.plugins?.[GIPITY_PLUGIN_ID];
258
259
  if (!Array.isArray(entries))
259
- return false;
260
- return entries.some((e) => e?.scope === 'user' &&
261
- typeof e?.version === 'string' &&
262
- versionGte(e.version, GIPITY_PLUGIN_VERSION));
260
+ return { exists: false, current: false };
261
+ const userEntries = entries.filter((e) => e?.scope === 'user');
262
+ return {
263
+ exists: userEntries.length > 0,
264
+ current: userEntries.some((e) => typeof e?.version === 'string' && versionGte(e.version, GIPITY_PLUGIN_VERSION)),
265
+ };
263
266
  }
264
267
  catch {
265
- return false;
268
+ return { exists: false, current: false };
266
269
  }
267
270
  }
271
+ /** True when Claude Code already records a USER-scope install of the Gipity
272
+ * plugin at >= the version this CLI needs - the common case, letting the
273
+ * caller skip the (slow) reinstall. Reads installed_plugins.json directly so
274
+ * the check costs no subprocess. Exported so `gipity status` can tell an
275
+ * actually-loaded plugin apart from one that's merely enabled-but-uninstalled
276
+ * (which would otherwise read as a false-green "hooks enabled"). */
277
+ export function userScopePluginCurrent() {
278
+ return userScopeInstallState().current;
279
+ }
268
280
  function claudeOnPath() {
269
281
  const probe = spawnSyncCommand(process.platform === 'win32' ? 'where' : 'which', ['claude'], {
270
282
  encoding: 'utf-8',
@@ -286,22 +298,31 @@ function claudeOnPath() {
286
298
  * never break `gipity claude`. Skips entirely when the user-scope install is
287
299
  * already current, so it shells out at most once per plugin-version bump. */
288
300
  export function ensureGipityPluginInstalled() {
289
- if (userScopePluginCurrent())
301
+ const state = userScopeInstallState();
302
+ if (state.current)
290
303
  return;
291
304
  if (!claudeOnPath())
292
305
  return;
293
- // Refresh the marketplace clone so `install` resolves the current version,
294
- // then (re)install at user scope - idempotent, and upgrades an older or
295
- // project-scoped install to the current one at user scope.
306
+ // Refresh the marketplace clone so install/update resolves the current version.
296
307
  // resolveCommand: on Windows `claude` is a .cmd shim that spawn can't launch
297
- // without an explicit path, so resolve it (otherwise the install silently
308
+ // without an explicit path, so resolve it (otherwise the command silently
298
309
  // ENOENTs and the plugin's hooks never land at user scope).
299
310
  const claudeCmd = resolveCommand('claude');
300
311
  spawnSyncCommand(claudeCmd, ['plugin', 'marketplace', 'update', GIPITY_MARKETPLACE_NAME], {
301
312
  stdio: 'ignore',
302
313
  timeout: 120_000,
303
314
  });
304
- spawnSyncCommand(claudeCmd, ['plugin', 'install', GIPITY_PLUGIN_ID, '--scope', 'user'], {
315
+ // A bare `plugin install` only materializes a MISSING install - on an
316
+ // already-present but stale user-scope install (the version this CLI just
317
+ // bumped past) it no-ops and leaves the old version registered, so
318
+ // userScopePluginCurrent() stays false forever and `gipity status` reports
319
+ // `missing: install` on every run. `plugin update` is the command that
320
+ // actually advances a registered user-scope install to the marketplace's
321
+ // current version; `install` is only right when nothing is installed yet.
322
+ const verb = state.exists
323
+ ? ['plugin', 'update', GIPITY_PLUGIN_ID, '--scope', 'user']
324
+ : ['plugin', 'install', GIPITY_PLUGIN_ID, '--scope', 'user'];
325
+ spawnSyncCommand(claudeCmd, verb, {
305
326
  stdio: 'ignore',
306
327
  timeout: 120_000,
307
328
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gipity",
3
- "version": "1.0.413",
3
+ "version": "1.0.416",
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",
@@ -13,7 +13,7 @@
13
13
  "postbuild": "node scripts/gen-build-info.mjs",
14
14
  "dev": "tsc --watch",
15
15
  "test": "npm run test:smoke",
16
- "test:smoke": "tsc && node --test dist/__tests__/utils.test.js dist/__tests__/platform.test.js dist/__tests__/colors.test.js dist/__tests__/config.test.js dist/__tests__/sync.test.js dist/__tests__/sync-apply.test.js dist/__tests__/sync-lock.test.js dist/__tests__/auth-lock.test.js dist/__tests__/api-401-retry.test.js dist/__tests__/push-cas.test.js dist/__tests__/upload.test.js dist/__tests__/progress.test.js dist/__tests__/updater.test.js dist/__tests__/cli-smoke.test.js dist/__tests__/claude-noninteractive.test.js dist/__tests__/claude-trust.test.js dist/__tests__/relay-state.test.js dist/__tests__/relay-daemon.test.js dist/__tests__/relay-installers.test.js dist/__tests__/relay-bridge-abort.test.js dist/__tests__/relay-redact.test.js dist/__tests__/relay-machine-id.test.js dist/__tests__/stream-json.test.js dist/__tests__/ingest-queue.test.js dist/__tests__/stream-delta.test.js dist/__tests__/phase-tracker.test.js dist/__tests__/relay-ingest-contract.test.js dist/__tests__/prompts.test.js dist/__tests__/capture-transcript.test.js dist/__tests__/flag-aliases.test.js dist/__tests__/client-context.test.js dist/__tests__/adopt-cwd.test.js dist/__tests__/cli-cmd-agent.test.js dist/__tests__/cli-cmd-approval.test.js dist/__tests__/cli-cmd-audit.test.js dist/__tests__/cli-cmd-chat.test.js dist/__tests__/cli-cmd-credits.test.js dist/__tests__/cli-cmd-db.test.js dist/__tests__/cli-cmd-deploy.test.js dist/__tests__/cli-cmd-domain.test.js dist/__tests__/cli-cmd-email.test.js dist/__tests__/cli-cmd-file.test.js dist/__tests__/cli-cmd-storage.test.js dist/__tests__/cli-cmd-fn.test.js dist/__tests__/cli-cmd-service.test.js dist/__tests__/cli-cmd-job.test.js dist/__tests__/cli-cmd-generate.test.js dist/__tests__/cli-cmd-gmail.test.js dist/__tests__/cli-cmd-info.test.js dist/__tests__/cli-cmd-init.test.js dist/__tests__/cli-cmd-location.test.js dist/__tests__/cli-cmd-text.test.js dist/__tests__/cli-cmd-login.test.js dist/__tests__/cli-cmd-logout.test.js dist/__tests__/cli-cmd-token.test.js dist/__tests__/cli-cmd-logs.test.js dist/__tests__/cli-cmd-memory.test.js dist/__tests__/cli-cmd-page.test.js dist/__tests__/cli-cmd-plan.test.js dist/__tests__/cli-cmd-project.test.js dist/__tests__/cli-cmd-rbac.test.js dist/__tests__/cli-cmd-realtime.test.js dist/__tests__/cli-cmd-records.test.js dist/__tests__/cli-cmd-relay.test.js dist/__tests__/cli-cmd-setup.test.js dist/__tests__/cli-cmd-doctor.test.js dist/__tests__/claude-setup.test.js dist/__tests__/cli-cmd-sandbox.test.js dist/__tests__/cli-cmd-add.test.js dist/__tests__/cli-cmd-remove.test.js dist/__tests__/cli-cmd-skill.test.js dist/__tests__/cli-cmd-test.test.js dist/__tests__/cli-cmd-workflow.test.js dist/__tests__/setup-skills-block.test.js dist/__tests__/setup-hooks.test.js",
16
+ "test:smoke": "tsc && node --test dist/__tests__/utils.test.js dist/__tests__/platform.test.js dist/__tests__/colors.test.js dist/__tests__/config.test.js dist/__tests__/sync.test.js dist/__tests__/sync-apply.test.js dist/__tests__/sync-lock.test.js dist/__tests__/auth-lock.test.js dist/__tests__/api-401-retry.test.js dist/__tests__/push-cas.test.js dist/__tests__/upload.test.js dist/__tests__/progress.test.js dist/__tests__/updater.test.js dist/__tests__/cli-smoke.test.js dist/__tests__/claude-noninteractive.test.js dist/__tests__/claude-trust.test.js dist/__tests__/relay-state.test.js dist/__tests__/relay-daemon.test.js dist/__tests__/relay-diagnostics.test.js dist/__tests__/relay-installers.test.js dist/__tests__/relay-bridge-abort.test.js dist/__tests__/relay-redact.test.js dist/__tests__/relay-machine-id.test.js dist/__tests__/stream-json.test.js dist/__tests__/ingest-queue.test.js dist/__tests__/stream-delta.test.js dist/__tests__/phase-tracker.test.js dist/__tests__/relay-ingest-contract.test.js dist/__tests__/prompts.test.js dist/__tests__/capture-transcript.test.js dist/__tests__/flag-aliases.test.js dist/__tests__/client-context.test.js dist/__tests__/adopt-cwd.test.js dist/__tests__/cli-cmd-agent.test.js dist/__tests__/cli-cmd-approval.test.js dist/__tests__/cli-cmd-audit.test.js dist/__tests__/cli-cmd-chat.test.js dist/__tests__/cli-cmd-credits.test.js dist/__tests__/cli-cmd-db.test.js dist/__tests__/cli-cmd-deploy.test.js dist/__tests__/cli-cmd-domain.test.js dist/__tests__/cli-cmd-email.test.js dist/__tests__/cli-cmd-file.test.js dist/__tests__/cli-cmd-storage.test.js dist/__tests__/cli-cmd-fn.test.js dist/__tests__/cli-cmd-service.test.js dist/__tests__/cli-cmd-job.test.js dist/__tests__/cli-cmd-generate.test.js dist/__tests__/cli-cmd-gmail.test.js dist/__tests__/cli-cmd-info.test.js dist/__tests__/cli-cmd-init.test.js dist/__tests__/cli-cmd-location.test.js dist/__tests__/cli-cmd-text.test.js dist/__tests__/cli-cmd-login.test.js dist/__tests__/cli-cmd-logout.test.js dist/__tests__/cli-cmd-token.test.js dist/__tests__/cli-cmd-logs.test.js dist/__tests__/cli-cmd-memory.test.js dist/__tests__/cli-cmd-page.test.js dist/__tests__/cli-cmd-plan.test.js dist/__tests__/cli-cmd-project.test.js dist/__tests__/cli-cmd-rbac.test.js dist/__tests__/cli-cmd-realtime.test.js dist/__tests__/cli-cmd-records.test.js dist/__tests__/cli-cmd-relay.test.js dist/__tests__/cli-cmd-setup.test.js dist/__tests__/cli-cmd-doctor.test.js dist/__tests__/claude-setup.test.js dist/__tests__/cli-cmd-sandbox.test.js dist/__tests__/cli-cmd-add.test.js dist/__tests__/cli-cmd-remove.test.js dist/__tests__/cli-cmd-skill.test.js dist/__tests__/cli-cmd-test.test.js dist/__tests__/cli-cmd-workflow.test.js dist/__tests__/setup-skills-block.test.js dist/__tests__/setup-hooks.test.js",
17
17
  "test:smoke:quick": "node scripts/smoke-quick.mjs",
18
18
  "test:e2e": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-live.test.js dist/__tests__/cli-e2e-sync-live.test.js dist/__tests__/cli-e2e-sync-stress-live.test.js dist/__tests__/cli-e2e-rollback-live.test.js dist/__tests__/cli-e2e-services-media-live.test.js dist/__tests__/cli-e2e-workflow-live.test.js dist/__tests__/cli-e2e-sandbox-live.test.js dist/__tests__/cli-e2e-page-fetch-live.test.js dist/__tests__/cli-e2e-page-test-live.test.js",
19
19
  "test:e2e:sync": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-sync-live.test.js",