waitspin 0.1.4 → 0.1.6

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,6 +46,10 @@ waitspin mimocode status
46
46
  # Or install for OpenCode TUI plugin slot
47
47
  waitspin opencode install --api-key PASTE_PUBLISHER_EXTENSION_KEY
48
48
  waitspin opencode status
49
+
50
+ # Or install for Grok Code CLI footer
51
+ waitspin grok install --api-key PASTE_PUBLISHER_EXTENSION_KEY
52
+ waitspin grok status
49
53
  ```
50
54
 
51
55
  ## Commands
@@ -74,15 +78,20 @@ waitspin opencode status
74
78
  - `waitspin opencode install` — install the OpenCode TUI plugin slot
75
79
  - `waitspin opencode status` — inspect managed OpenCode runtime state
76
80
  - `waitspin opencode uninstall` — remove managed OpenCode runtime and plugin
81
+ - `waitspin grok install` — install the Grok Code CLI footer surface
82
+ - `waitspin grok status` — inspect managed Grok Code CLI runtime state
83
+ - `waitspin grok uninstall` — restore Grok Code CLI and remove managed state
77
84
 
78
85
  API base: `https://api.waitspin.com`
79
86
 
80
- The public package installs four verified publisher targets: the VS Code
87
+ The public package installs five verified publisher targets: the VS Code
81
88
  status-bar fallback, the Claude Code statusline command, the MiMo Code shell
82
- hook, and the OpenCode TUI plugin slot. Claude Code support uses the official
83
- `statusLine.command` path and does not patch Claude Code internals. MiMo Code
84
- uses a bash hook that polls the API for sponsored messages. OpenCode uses its
85
- TUI `app_bottom` plugin slot.
89
+ hook, the OpenCode TUI plugin slot, and the Grok Code CLI footer. Claude Code
90
+ support uses the official `statusLine.command` path and does not patch Claude
91
+ Code internals. MiMo Code uses a bash hook that polls the API for sponsored
92
+ messages. OpenCode uses its TUI `app_bottom` plugin slot. Grok Code CLI uses a
93
+ managed text-asset footer patch with hash-backed backup/restore and does not
94
+ patch native binaries.
86
95
 
87
96
  `waitspin install --all` is an advanced agent command for installing every
88
97
  detected supported target. It keeps explicit target commands as the canonical
@@ -112,6 +121,7 @@ npx waitspin extension install --target vscode --api-key PASTE_PUBLISHER_EXTENSI
112
121
  npx waitspin claude-code install --api-key PASTE_PUBLISHER_EXTENSION_KEY --compose-existing
113
122
  npx waitspin mimocode install --api-key PASTE_PUBLISHER_EXTENSION_KEY
114
123
  npx waitspin opencode install --api-key PASTE_PUBLISHER_EXTENSION_KEY
124
+ npx waitspin grok install --api-key PASTE_PUBLISHER_EXTENSION_KEY
115
125
  ```
116
126
 
117
127
  - `WAITSPIN_API_KEY` — temporary publisher-extension key for first activation
@@ -136,6 +146,14 @@ The OpenCode installer writes a managed runtime/state under `~/.waitspin`,
136
146
  installs a TUI plugin under `~/.config/opencode/plugins`, and adds the managed
137
147
  entry to `~/.config/opencode/tui.json` so OpenCode mounts the `app_bottom` slot.
138
148
 
149
+ The Grok Code CLI installer writes a managed runtime/state under `~/.waitspin`
150
+ and patches the verified OpenTUI footer text asset with a hash-backed backup so
151
+ uninstall can restore the original file.
152
+
153
+ Cline VS Code extension installs are covered by the VS Code fallback target.
154
+ Standalone Cline CLI is not a public install target until Cline exposes an
155
+ official statusline/plugin surface.
156
+
139
157
  ## Release validation
140
158
 
141
159
  Before advertising the public `npx waitspin` path, release operators should run:
@@ -147,4 +165,5 @@ npm view waitspin version
147
165
  npx --yes waitspin --help
148
166
  npx --yes waitspin@latest mimocode status
149
167
  npx --yes waitspin@latest opencode status
168
+ npx --yes waitspin@latest grok status
150
169
  ```
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import { constants as fsConstants } from "node:fs";
6
6
  import { access, chmod, cp, mkdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
7
7
  import os from "node:os";
8
8
  import path from "node:path";
9
+ import { experimentalAllInstallTargets, experimentalInstallTarget, isExperimentalCliTargetName, runExperimentalCliTargetInstall, runExperimentalCliTargetStatus, runExperimentalCliTargetUninstall, } from "./targets/experimental-cli.js";
9
10
  const DEFAULT_BASE_URL = process.env.WAITSPIN_BASE_URL?.trim() || "https://api.waitspin.com";
10
11
  const PRODUCTION_API_ORIGIN = "https://api.waitspin.com";
11
12
  const REQUEST_TIMEOUT_MS = 30_000;
@@ -50,11 +51,14 @@ export function usageText() {
50
51
  " waitspin opencode install [--api-key KEY] [--dry-run]",
51
52
  " waitspin opencode status",
52
53
  " waitspin opencode uninstall [--dry-run]",
54
+ " waitspin grok install [--api-key KEY] [--dry-run]",
55
+ " waitspin grok status",
56
+ " waitspin grok uninstall [--dry-run]",
53
57
  "",
54
58
  "Defaults:",
55
59
  " API base: https://api.waitspin.com",
56
60
  " API key: WAITSPIN_API_KEY env var",
57
- " Public publisher targets: status-bar-fallback, claude-code, mimocode, opencode",
61
+ " Public publisher targets: status-bar-fallback, claude-code, mimocode, opencode, grok",
58
62
  ].join("\n") + "\n");
59
63
  }
60
64
  function usage(exitCode = 1) {
@@ -95,6 +99,7 @@ function parseArgs(argv) {
95
99
  key === "allow-dev-extension-assets" ||
96
100
  key === "confirm-test-transfer" ||
97
101
  key === "compose-existing" ||
102
+ key === "include-experimental" ||
98
103
  key === "all") {
99
104
  flags.set(key, ["true"]);
100
105
  continue;
@@ -607,6 +612,20 @@ async function registerPublisherInstall(input) {
607
612
  "For developer-local acceptance only, use an explicit loopback API with --allow-dev-api-base.",
608
613
  ].join("\n"));
609
614
  }
615
+ if (isExperimentalCliTargetName(input.target) &&
616
+ error instanceof WaitSpinCliHttpError &&
617
+ error.status === 400 &&
618
+ /Invalid input|Validation error/.test(error.message)) {
619
+ throw new Error([
620
+ `WaitSpin API rejected target "${input.target}".`,
621
+ "Your local CLI supports this hidden experimental target, but the selected API base does not.",
622
+ "",
623
+ "For production:",
624
+ " deploy the backend target allowlist, then rerun the explicit target install.",
625
+ "",
626
+ "For developer-local acceptance only, use an explicit loopback API with --allow-dev-api-base.",
627
+ ].join("\n"));
628
+ }
610
629
  throw error;
611
630
  }
612
631
  if (!body) {
@@ -2722,8 +2741,20 @@ async function preflightMiMoCode() {
2722
2741
  async function preflightOpenCode() {
2723
2742
  return executableVersion(OPENCODE_BIN_ENV, OPENCODE_DEFAULT_BIN, "OpenCode");
2724
2743
  }
2725
- function allInstallTargets() {
2726
- return [
2744
+ function experimentalCliDeps() {
2745
+ return {
2746
+ booleanFlag,
2747
+ optionalFlag,
2748
+ resolveCredentialedBaseUrl,
2749
+ requireApiKey,
2750
+ registerPublisherInstall,
2751
+ generateInstallId,
2752
+ printJson,
2753
+ };
2754
+ }
2755
+ function allInstallTargets(flags) {
2756
+ const experimentalDeps = experimentalCliDeps();
2757
+ const targets = [
2727
2758
  {
2728
2759
  target: "vscode",
2729
2760
  command: "waitspin extension install --target vscode",
@@ -2756,7 +2787,12 @@ function allInstallTargets() {
2756
2787
  install: runOpencodeInstall,
2757
2788
  status: () => runOpencodeStatus(),
2758
2789
  },
2790
+ experimentalInstallTarget("grok", experimentalDeps),
2759
2791
  ];
2792
+ if (booleanFlag(flags, "include-experimental")) {
2793
+ targets.push(...experimentalAllInstallTargets(experimentalDeps).filter((target) => target.target !== "grok"));
2794
+ }
2795
+ return targets;
2760
2796
  }
2761
2797
  function safeErrorMessage(error) {
2762
2798
  const message = error instanceof Error ? error.message : String(error);
@@ -2765,11 +2801,17 @@ function safeErrorMessage(error) {
2765
2801
  .replace(/npm_[A-Za-z0-9_-]+/g, "[REDACTED_NPM_TOKEN]");
2766
2802
  }
2767
2803
  function isNotDetectedError(message) {
2768
- return /not detected|not found|Unable to run Claude Code|Unsupported Claude Code version|ENOENT|spawn .*ENOENT|package not found/i.test(message);
2804
+ if (/WaitSpin extension package not found|assets not found/i.test(message)) {
2805
+ return false;
2806
+ }
2807
+ return /not detected|Unable to run Claude Code|Unsupported Claude Code version|ENOENT|spawn .*ENOENT|command not found|executable path/i.test(message);
2769
2808
  }
2770
2809
  function isConflictError(message) {
2771
2810
  return /statusLine|status line|conflict|override|already has|blocked/i.test(message);
2772
2811
  }
2812
+ function isUnsupportedTargetLayoutReason(reason) {
2813
+ return /unsupported_patch_layout|unsupported_native_cli/i.test(reason);
2814
+ }
2773
2815
  function objectRecord(value) {
2774
2816
  return value && typeof value === "object" && !Array.isArray(value)
2775
2817
  ? value
@@ -2779,24 +2821,50 @@ function dryRunConflictReason(result) {
2779
2821
  const record = objectRecord(result);
2780
2822
  if (!record.would_fail)
2781
2823
  return null;
2782
- const reason = record.settings_blocked_reason ?? record.human_message;
2824
+ if (typeof record.failure_kind === "string" &&
2825
+ isUnsupportedTargetLayoutReason(record.failure_kind)) {
2826
+ return typeof record.human_message === "string"
2827
+ ? record.human_message
2828
+ : record.failure_kind;
2829
+ }
2830
+ const reason = record.settings_blocked_reason ??
2831
+ (record.failure_kind ? null : record.human_message);
2832
+ if (!reason)
2833
+ return null;
2783
2834
  return typeof reason === "string" ? reason : "target reported a conflict";
2784
2835
  }
2836
+ function dryRunFailureReason(result) {
2837
+ const record = objectRecord(result);
2838
+ if (!record.would_fail)
2839
+ return null;
2840
+ const reason = record.failure_kind ??
2841
+ record.rollback_reason ??
2842
+ record.human_message ??
2843
+ "target dry-run reported failure";
2844
+ return typeof reason === "string" ? reason : "target dry-run reported failure";
2845
+ }
2785
2846
  export async function runInstallAll(flags) {
2786
2847
  const dryRun = booleanFlag(flags, "dry-run");
2848
+ const includeExperimental = booleanFlag(flags, "include-experimental");
2849
+ if (includeExperimental && !dryRun) {
2850
+ throw new Error("--include-experimental is only available with install --all --dry-run. Use explicit waitspin <target> install commands for hidden experimental targets.");
2851
+ }
2787
2852
  const installed = [];
2788
2853
  const wouldInstall = [];
2789
2854
  const skippedNotDetected = [];
2790
2855
  const skippedConflict = [];
2791
2856
  const failedRollback = [];
2792
- for (const target of allInstallTargets()) {
2857
+ for (const target of allInstallTargets(flags)) {
2793
2858
  let detail = null;
2794
2859
  try {
2795
2860
  detail = await target.preflight(flags);
2796
2861
  }
2797
2862
  catch (error) {
2798
2863
  const reason = safeErrorMessage(error);
2799
- skippedNotDetected.push({
2864
+ const bucket = isNotDetectedError(reason)
2865
+ ? skippedNotDetected
2866
+ : failedRollback;
2867
+ bucket.push({
2800
2868
  target: target.target,
2801
2869
  command: target.command,
2802
2870
  reason,
@@ -2816,6 +2884,17 @@ export async function runInstallAll(flags) {
2816
2884
  });
2817
2885
  continue;
2818
2886
  }
2887
+ const failureReason = dryRunFailureReason(result);
2888
+ if (failureReason) {
2889
+ failedRollback.push({
2890
+ target: target.target,
2891
+ command: target.command,
2892
+ reason: failureReason,
2893
+ detail,
2894
+ result,
2895
+ });
2896
+ continue;
2897
+ }
2819
2898
  const summary = {
2820
2899
  target: target.target,
2821
2900
  command: target.command,
@@ -2833,9 +2912,11 @@ export async function runInstallAll(flags) {
2833
2912
  const reason = safeErrorMessage(error);
2834
2913
  const bucket = isConflictError(reason)
2835
2914
  ? skippedConflict
2836
- : isNotDetectedError(reason)
2837
- ? skippedNotDetected
2838
- : failedRollback;
2915
+ : isUnsupportedTargetLayoutReason(reason)
2916
+ ? skippedConflict
2917
+ : isNotDetectedError(reason)
2918
+ ? skippedNotDetected
2919
+ : failedRollback;
2839
2920
  bucket.push({
2840
2921
  target: target.target,
2841
2922
  command: target.command,
@@ -2849,13 +2930,16 @@ export async function runInstallAll(flags) {
2849
2930
  command: "install --all",
2850
2931
  dry_run: dryRun,
2851
2932
  mode: "detected-targets",
2933
+ include_experimental: includeExperimental,
2852
2934
  installed,
2853
2935
  would_install: wouldInstall,
2854
2936
  skipped_not_detected: skippedNotDetected,
2855
2937
  skipped_conflict: skippedConflict,
2856
2938
  failed_rollback: failedRollback,
2857
2939
  next: "check_all_status",
2858
- next_command: "waitspin status --all",
2940
+ next_command: includeExperimental
2941
+ ? "waitspin status --all --include-experimental"
2942
+ : "waitspin status --all",
2859
2943
  human_message: "Install-all is an advanced agent command. Explicit target commands remain the canonical debug path.",
2860
2944
  });
2861
2945
  }
@@ -2863,7 +2947,7 @@ export async function runStatusAll(flags) {
2863
2947
  const statuses = [];
2864
2948
  const installed = [];
2865
2949
  const failedStatus = [];
2866
- for (const target of allInstallTargets()) {
2950
+ for (const target of allInstallTargets(flags)) {
2867
2951
  try {
2868
2952
  const result = await capturePrintedJson(() => target.status(flags));
2869
2953
  const summary = {
@@ -2887,6 +2971,7 @@ export async function runStatusAll(flags) {
2887
2971
  printJson({
2888
2972
  ok: failedStatus.length === 0,
2889
2973
  command: "status --all",
2974
+ include_experimental: booleanFlag(flags, "include-experimental"),
2890
2975
  installed,
2891
2976
  statuses,
2892
2977
  failed_status: failedStatus,
@@ -2986,6 +3071,18 @@ export async function main(argv = process.argv.slice(2)) {
2986
3071
  await runOpencodeUninstall(flags);
2987
3072
  return;
2988
3073
  }
3074
+ if (isExperimentalCliTargetName(command) && positionals[0] === "install") {
3075
+ await runExperimentalCliTargetInstall(command, flags, experimentalCliDeps());
3076
+ return;
3077
+ }
3078
+ if (isExperimentalCliTargetName(command) && positionals[0] === "status") {
3079
+ await runExperimentalCliTargetStatus(command, experimentalCliDeps());
3080
+ return;
3081
+ }
3082
+ if (isExperimentalCliTargetName(command) && positionals[0] === "uninstall") {
3083
+ await runExperimentalCliTargetUninstall(command, flags, experimentalCliDeps());
3084
+ return;
3085
+ }
2989
3086
  usage();
2990
3087
  }
2991
3088
  function isDirectEntrypoint() {