waitspin 0.1.5 → 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);
@@ -2773,6 +2809,9 @@ function isNotDetectedError(message) {
2773
2809
  function isConflictError(message) {
2774
2810
  return /statusLine|status line|conflict|override|already has|blocked/i.test(message);
2775
2811
  }
2812
+ function isUnsupportedTargetLayoutReason(reason) {
2813
+ return /unsupported_patch_layout|unsupported_native_cli/i.test(reason);
2814
+ }
2776
2815
  function objectRecord(value) {
2777
2816
  return value && typeof value === "object" && !Array.isArray(value)
2778
2817
  ? value
@@ -2782,17 +2821,40 @@ function dryRunConflictReason(result) {
2782
2821
  const record = objectRecord(result);
2783
2822
  if (!record.would_fail)
2784
2823
  return null;
2785
- 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;
2786
2834
  return typeof reason === "string" ? reason : "target reported a conflict";
2787
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
+ }
2788
2846
  export async function runInstallAll(flags) {
2789
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
+ }
2790
2852
  const installed = [];
2791
2853
  const wouldInstall = [];
2792
2854
  const skippedNotDetected = [];
2793
2855
  const skippedConflict = [];
2794
2856
  const failedRollback = [];
2795
- for (const target of allInstallTargets()) {
2857
+ for (const target of allInstallTargets(flags)) {
2796
2858
  let detail = null;
2797
2859
  try {
2798
2860
  detail = await target.preflight(flags);
@@ -2822,6 +2884,17 @@ export async function runInstallAll(flags) {
2822
2884
  });
2823
2885
  continue;
2824
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
+ }
2825
2898
  const summary = {
2826
2899
  target: target.target,
2827
2900
  command: target.command,
@@ -2839,9 +2912,11 @@ export async function runInstallAll(flags) {
2839
2912
  const reason = safeErrorMessage(error);
2840
2913
  const bucket = isConflictError(reason)
2841
2914
  ? skippedConflict
2842
- : isNotDetectedError(reason)
2843
- ? skippedNotDetected
2844
- : failedRollback;
2915
+ : isUnsupportedTargetLayoutReason(reason)
2916
+ ? skippedConflict
2917
+ : isNotDetectedError(reason)
2918
+ ? skippedNotDetected
2919
+ : failedRollback;
2845
2920
  bucket.push({
2846
2921
  target: target.target,
2847
2922
  command: target.command,
@@ -2855,13 +2930,16 @@ export async function runInstallAll(flags) {
2855
2930
  command: "install --all",
2856
2931
  dry_run: dryRun,
2857
2932
  mode: "detected-targets",
2933
+ include_experimental: includeExperimental,
2858
2934
  installed,
2859
2935
  would_install: wouldInstall,
2860
2936
  skipped_not_detected: skippedNotDetected,
2861
2937
  skipped_conflict: skippedConflict,
2862
2938
  failed_rollback: failedRollback,
2863
2939
  next: "check_all_status",
2864
- next_command: "waitspin status --all",
2940
+ next_command: includeExperimental
2941
+ ? "waitspin status --all --include-experimental"
2942
+ : "waitspin status --all",
2865
2943
  human_message: "Install-all is an advanced agent command. Explicit target commands remain the canonical debug path.",
2866
2944
  });
2867
2945
  }
@@ -2869,7 +2947,7 @@ export async function runStatusAll(flags) {
2869
2947
  const statuses = [];
2870
2948
  const installed = [];
2871
2949
  const failedStatus = [];
2872
- for (const target of allInstallTargets()) {
2950
+ for (const target of allInstallTargets(flags)) {
2873
2951
  try {
2874
2952
  const result = await capturePrintedJson(() => target.status(flags));
2875
2953
  const summary = {
@@ -2893,6 +2971,7 @@ export async function runStatusAll(flags) {
2893
2971
  printJson({
2894
2972
  ok: failedStatus.length === 0,
2895
2973
  command: "status --all",
2974
+ include_experimental: booleanFlag(flags, "include-experimental"),
2896
2975
  installed,
2897
2976
  statuses,
2898
2977
  failed_status: failedStatus,
@@ -2992,6 +3071,18 @@ export async function main(argv = process.argv.slice(2)) {
2992
3071
  await runOpencodeUninstall(flags);
2993
3072
  return;
2994
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
+ }
2995
3086
  usage();
2996
3087
  }
2997
3088
  function isDirectEntrypoint() {