contextspin 0.5.1 → 0.6.0

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
@@ -46,23 +46,33 @@ ContextSpin polls those sources on a schedule, formats whatever they return into
46
46
 
47
47
  The daemon and the injector are decoupled by the cache file: the daemon writes snippets, the injector reads them. Each runs on its own clock.
48
48
 
49
- ## Install / Quickstart
49
+ ## Install
50
50
 
51
51
  Requires Node.js >= 18 (ContextSpin uses the built-in global `fetch`).
52
52
 
53
+ **One line — that's it:**
54
+
53
55
  ```bash
54
- # 1. Create a config (interactive, or non-interactive with --yes)
55
- npx contextspin setup
56
+ curl -fsSL https://raw.githubusercontent.com/mannutech/contextspin/main/install.sh | bash
57
+ ```
58
+
59
+ This wires a SessionStart hook into `~/.claude/settings.json` (so ContextSpin self-heals every session), seeds a no-credentials starter pack (weather, a dad joke, the top Hacker News story), and wires your status bar — non-destructively. Restart Claude Code and you'll see live snippets, never an empty bar.
56
60
 
57
- # 2. Start the background polling daemon
58
- npx contextspin start
61
+ Prefer to do it yourself? `npx contextspin install` does the same thing. To remove everything: `npx contextspin uninstall`.
59
62
 
60
- # 3. Wire the snippets into the Claude Code status bar
61
- npx contextspin inject
63
+ <details>
64
+ <summary>Manual / advanced setup</summary>
65
+
66
+ ```bash
67
+ npx contextspin setup # create a config (add --yes to skip prompts)
68
+ npx contextspin start # start the background polling daemon
69
+ npx contextspin inject # wire snippets into the status bar
62
70
  ```
63
71
 
64
72
  Running `npx contextspin` with **no subcommand** is a shortcut: if no config exists it runs `setup`, otherwise it runs `start` followed by `inject` using the mode from your config.
65
73
 
74
+ </details>
75
+
66
76
  Check what's happening at any time:
67
77
 
68
78
  ```bash
package/install.sh ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ # ContextSpin one-line installer.
3
+ #
4
+ # curl -fsSL https://raw.githubusercontent.com/mannutech/contextspin/main/install.sh | bash
5
+ #
6
+ # Wires a SessionStart hook into ~/.claude/settings.json (so ContextSpin
7
+ # self-heals every session) and sets up the config, statusline, and daemon.
8
+ # Non-destructive: any existing statusline is composed, not replaced.
9
+ set -euo pipefail
10
+
11
+ if ! command -v node >/dev/null 2>&1; then
12
+ echo "ContextSpin needs Node.js 18+ — install it from https://nodejs.org and re-run." >&2
13
+ exit 1
14
+ fi
15
+
16
+ MAJOR="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)"
17
+ if [ "${MAJOR}" -lt 18 ]; then
18
+ echo "ContextSpin needs Node.js 18+, but found $(node -v). Please upgrade." >&2
19
+ exit 1
20
+ fi
21
+
22
+ echo "Installing ContextSpin…"
23
+ # Run from a neutral dir so npx never resolves a confused local package.
24
+ cd /tmp
25
+ npx --yes contextspin@latest install
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contextspin",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Replace Claude Code spinner/statusline text with live org context (meetings, Slack, CI, incidents, PRs) aggregated from your existing MCP servers, CLIs, and HTTP endpoints.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,6 +35,7 @@
35
35
  "src",
36
36
  "README.md",
37
37
  "LICENSE",
38
+ "install.sh",
38
39
  ".contextspin.example.json"
39
40
  ]
40
41
  }
package/src/cli.js CHANGED
@@ -472,6 +472,123 @@ async function runUninject(opts = {}) {
472
472
  }
473
473
  }
474
474
 
475
+ /**
476
+ * The SessionStart hook command the `install` flow wires into the user settings
477
+ * so ContextSpin self-heals every session (the curl install replicates what the
478
+ * Claude Code plugin's hook does, without the marketplace). Runs from a neutral
479
+ * dir so npx never resolves a confused local package (Exit 127).
480
+ */
481
+ const SESSIONSTART_HOOK_CMD =
482
+ 'cd /tmp && npx --yes contextspin ensure >/dev/null 2>&1; exit 0';
483
+
484
+ /**
485
+ * Read+parse a JSON file, returning a fallback on any read/parse error.
486
+ * @param {string} filePath
487
+ * @param {*} fallback
488
+ * @returns {*}
489
+ */
490
+ function readJsonSafeSync(filePath, fallback) {
491
+ try {
492
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
493
+ } catch {
494
+ return fallback;
495
+ }
496
+ }
497
+
498
+ /**
499
+ * Whether a SessionStart entry already runs ContextSpin (so install is idempotent).
500
+ * @param {*} entry
501
+ * @returns {boolean}
502
+ */
503
+ function entryRunsContextspin(entry) {
504
+ return !!(
505
+ entry &&
506
+ Array.isArray(entry.hooks) &&
507
+ entry.hooks.some(
508
+ (h) => h && typeof h.command === 'string' && h.command.includes('contextspin'),
509
+ )
510
+ );
511
+ }
512
+
513
+ /**
514
+ * Wire a ContextSpin SessionStart hook into the user ~/.claude/settings.json so
515
+ * the daemon + statusline self-heal every session. JSON-merge (preserves every
516
+ * other key and any existing hooks). Idempotent.
517
+ * @returns {boolean} true if the hook was added (false if already present).
518
+ */
519
+ function addSessionStartHook() {
520
+ fs.mkdirSync(path.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
521
+ const settings = readJsonSafeSync(CLAUDE_SETTINGS_PATH, {});
522
+ const obj = settings && typeof settings === 'object' ? settings : {};
523
+ obj.hooks = obj.hooks && typeof obj.hooks === 'object' ? obj.hooks : {};
524
+ const arr = Array.isArray(obj.hooks.SessionStart) ? obj.hooks.SessionStart : [];
525
+ if (arr.some(entryRunsContextspin)) return false;
526
+ arr.push({
527
+ matcher: '',
528
+ hooks: [{ type: 'command', command: SESSIONSTART_HOOK_CMD, timeout: 15 }],
529
+ });
530
+ obj.hooks.SessionStart = arr;
531
+ fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(obj, null, 2));
532
+ return true;
533
+ }
534
+
535
+ /**
536
+ * Remove any ContextSpin SessionStart hook from the user settings (best-effort,
537
+ * JSON-merge). Prunes empty containers.
538
+ * @returns {boolean} true if a hook was removed.
539
+ */
540
+ function removeSessionStartHook() {
541
+ const settings = readJsonSafeSync(CLAUDE_SETTINGS_PATH, null);
542
+ if (!settings || typeof settings !== 'object' || !settings.hooks) return false;
543
+ const arr = Array.isArray(settings.hooks.SessionStart)
544
+ ? settings.hooks.SessionStart
545
+ : [];
546
+ const kept = arr.filter((e) => !entryRunsContextspin(e));
547
+ if (kept.length === arr.length) return false;
548
+ if (kept.length > 0) settings.hooks.SessionStart = kept;
549
+ else delete settings.hooks.SessionStart;
550
+ if (settings.hooks && Object.keys(settings.hooks).length === 0) {
551
+ delete settings.hooks;
552
+ }
553
+ fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
554
+ return true;
555
+ }
556
+
557
+ /**
558
+ * One-shot install (the `curl | bash` entrypoint): wire the SessionStart hook so
559
+ * ContextSpin self-heals each session, then run ensure (config + statusline +
560
+ * daemon) so it's live immediately. Prints a friendly summary.
561
+ * @returns {Promise<void>}
562
+ */
563
+ async function runInstall() {
564
+ const addedHook = addSessionStartHook();
565
+ await runEnsure();
566
+ console.log('');
567
+ console.log(
568
+ addedHook
569
+ ? '✨ ContextSpin installed — it auto-refreshes every Claude Code session.'
570
+ : '✨ ContextSpin already installed (SessionStart hook present).',
571
+ );
572
+ console.log(' Restart Claude Code to see your statusline.');
573
+ console.log(' Check it anytime: contextspin status');
574
+ }
575
+
576
+ /**
577
+ * Full uninstall: remove the SessionStart hook, the statusline wiring, and stop
578
+ * the daemon.
579
+ * @returns {Promise<void>}
580
+ */
581
+ async function runUninstall() {
582
+ const removedHook = removeSessionStartHook();
583
+ await uninstallStatusline({});
584
+ await stopDaemon();
585
+ console.log(
586
+ removedHook
587
+ ? 'ContextSpin uninstalled (hook removed, statusline restored, daemon stopped).'
588
+ : 'ContextSpin hook not found; statusline restored and daemon stopped.',
589
+ );
590
+ }
591
+
475
592
  /**
476
593
  * The default action when no subcommand is given: set up if there is no config,
477
594
  * otherwise start the daemon and inject per the configured mode.
@@ -508,6 +625,18 @@ function buildProgram() {
508
625
  .option('--yes', 'skip prompts and write a detected config')
509
626
  .action(action(async (opts) => runSetup(opts)));
510
627
 
628
+ program
629
+ .command('install')
630
+ .description(
631
+ 'One-line install: wire the SessionStart hook so ContextSpin self-heals each session, then set up config + statusline + daemon',
632
+ )
633
+ .action(action(async () => runInstall()));
634
+
635
+ program
636
+ .command('uninstall')
637
+ .description('Remove ContextSpin entirely (SessionStart hook, statusline, daemon)')
638
+ .action(action(async () => runUninstall()));
639
+
511
640
  program
512
641
  .command('ensure')
513
642
  .description(