@hwip/cli 0.0.1 → 0.1.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/LICENSE +190 -0
- package/NOTICE +11 -0
- package/README.md +1979 -0
- package/dist/agent-list.d.ts +76 -0
- package/dist/agent-list.js +102 -0
- package/dist/agent-read.d.ts +105 -0
- package/dist/agent-read.js +309 -0
- package/dist/agent-rules-fetch.d.ts +51 -0
- package/dist/agent-rules-fetch.js +275 -0
- package/dist/agent-rules.default.d.ts +30 -0
- package/dist/agent-rules.default.js +292 -0
- package/dist/agent-session.d.ts +23 -0
- package/dist/agent-session.js +44 -0
- package/dist/agent-state.d.ts +84 -0
- package/dist/agent-state.js +188 -0
- package/dist/agent-target.d.ts +60 -0
- package/dist/agent-target.js +81 -0
- package/dist/agents.d.ts +31 -0
- package/dist/agents.js +75 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +76 -0
- package/dist/args.d.ts +74 -0
- package/dist/args.js +254 -0
- package/dist/ask-context-attach.d.ts +73 -0
- package/dist/ask-context-attach.js +151 -0
- package/dist/ask-context-screen.d.ts +21 -0
- package/dist/ask-context-screen.js +49 -0
- package/dist/ask-context-switch.d.ts +31 -0
- package/dist/ask-context-switch.js +74 -0
- package/dist/ask-context.d.ts +84 -0
- package/dist/ask-context.js +172 -0
- package/dist/ask-pending.d.ts +47 -0
- package/dist/ask-pending.js +221 -0
- package/dist/ask-seal.d.ts +120 -0
- package/dist/ask-seal.js +179 -0
- package/dist/ask.d.ts +107 -0
- package/dist/ask.js +551 -0
- package/dist/blocked-prompt-seal.d.ts +35 -0
- package/dist/blocked-prompt-seal.js +89 -0
- package/dist/blocked-prompt.d.ts +57 -0
- package/dist/blocked-prompt.js +181 -0
- package/dist/channel.d.ts +67 -0
- package/dist/channel.js +136 -0
- package/dist/check-update.d.ts +28 -0
- package/dist/check-update.js +153 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +813 -0
- package/dist/codex-config.d.ts +24 -0
- package/dist/codex-config.js +169 -0
- package/dist/codex-transcript.d.ts +59 -0
- package/dist/codex-transcript.js +373 -0
- package/dist/command-refusal.d.ts +56 -0
- package/dist/command-refusal.js +127 -0
- package/dist/command-scan.d.ts +46 -0
- package/dist/command-scan.js +154 -0
- package/dist/command-seal.d.ts +57 -0
- package/dist/command-seal.js +88 -0
- package/dist/command-seq.d.ts +30 -0
- package/dist/command-seq.js +150 -0
- package/dist/config.d.ts +156 -0
- package/dist/config.js +392 -0
- package/dist/core-sections.d.ts +18 -0
- package/dist/core-sections.js +28 -0
- package/dist/crypto.d.ts +278 -0
- package/dist/crypto.js +698 -0
- package/dist/desktop-notify.d.ts +33 -0
- package/dist/desktop-notify.js +49 -0
- package/dist/device-directory.d.ts +36 -0
- package/dist/device-directory.js +97 -0
- package/dist/device-presence.d.ts +17 -0
- package/dist/device-presence.js +62 -0
- package/dist/downloads.d.ts +29 -0
- package/dist/downloads.js +106 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +28 -0
- package/dist/file-crypto.d.ts +94 -0
- package/dist/file-crypto.js +272 -0
- package/dist/file-view-budget.d.ts +15 -0
- package/dist/file-view-budget.js +34 -0
- package/dist/file-view-switch.d.ts +28 -0
- package/dist/file-view-switch.js +122 -0
- package/dist/file-view.d.ts +108 -0
- package/dist/file-view.js +327 -0
- package/dist/gate.d.ts +141 -0
- package/dist/gate.js +336 -0
- package/dist/gemini-transcript.d.ts +32 -0
- package/dist/gemini-transcript.js +189 -0
- package/dist/http-failure.d.ts +98 -0
- package/dist/http-failure.js +349 -0
- package/dist/hwip-core.generated.d.ts +7 -0
- package/dist/hwip-core.generated.js +14 -0
- package/dist/hwip-hook.d.ts +77 -0
- package/dist/hwip-hook.js +340 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/input-sequencer.d.ts +65 -0
- package/dist/input-sequencer.js +142 -0
- package/dist/insecure-url.d.ts +16 -0
- package/dist/insecure-url.js +76 -0
- package/dist/installer.d.ts +96 -0
- package/dist/installer.js +850 -0
- package/dist/inventory-offload.d.ts +49 -0
- package/dist/inventory-offload.js +209 -0
- package/dist/inventory-worker.d.ts +2 -0
- package/dist/inventory-worker.js +27 -0
- package/dist/jsonc-doc.d.ts +106 -0
- package/dist/jsonc-doc.js +354 -0
- package/dist/keep-awake.d.ts +32 -0
- package/dist/keep-awake.js +46 -0
- package/dist/key-changes.d.ts +48 -0
- package/dist/key-changes.js +140 -0
- package/dist/lan-auth.d.ts +140 -0
- package/dist/lan-auth.js +217 -0
- package/dist/lan-endpoint.d.ts +41 -0
- package/dist/lan-endpoint.js +115 -0
- package/dist/lan-receiver.d.ts +122 -0
- package/dist/lan-receiver.js +510 -0
- package/dist/lan-sender.d.ts +76 -0
- package/dist/lan-sender.js +184 -0
- package/dist/lan-transfer.d.ts +36 -0
- package/dist/lan-transfer.js +54 -0
- package/dist/listener-device-id.d.ts +20 -0
- package/dist/listener-device-id.js +133 -0
- package/dist/listener-log.d.ts +4 -0
- package/dist/listener-log.js +23 -0
- package/dist/listener-process.d.ts +200 -0
- package/dist/listener-process.js +470 -0
- package/dist/listener-service.d.ts +225 -0
- package/dist/listener-service.js +682 -0
- package/dist/listener-worktree-cleanup.d.ts +27 -0
- package/dist/listener-worktree-cleanup.js +62 -0
- package/dist/listener.d.ts +1325 -0
- package/dist/listener.js +5680 -0
- package/dist/login-address.d.ts +25 -0
- package/dist/login-address.js +97 -0
- package/dist/login-page.d.ts +29 -0
- package/dist/login-page.js +93 -0
- package/dist/login.d.ts +98 -0
- package/dist/login.js +388 -0
- package/dist/mcp-command.d.ts +45 -0
- package/dist/mcp-command.js +50 -0
- package/dist/mcp.d.ts +19 -0
- package/dist/mcp.js +29 -0
- package/dist/node-support.d.ts +19 -0
- package/dist/node-support.js +38 -0
- package/dist/notify-failure.d.ts +57 -0
- package/dist/notify-failure.js +150 -0
- package/dist/opencode-transcript.d.ts +139 -0
- package/dist/opencode-transcript.js +390 -0
- package/dist/optional-features.d.ts +6 -0
- package/dist/optional-features.js +19 -0
- package/dist/pane-fit.d.ts +40 -0
- package/dist/pane-fit.js +119 -0
- package/dist/peer-key.d.ts +33 -0
- package/dist/peer-key.js +34 -0
- package/dist/pi-transcript.d.ts +26 -0
- package/dist/pi-transcript.js +255 -0
- package/dist/presence.d.ts +14 -0
- package/dist/presence.js +124 -0
- package/dist/private-files.d.ts +51 -0
- package/dist/private-files.js +218 -0
- package/dist/project-switch.d.ts +19 -0
- package/dist/project-switch.js +60 -0
- package/dist/redact.d.ts +10 -0
- package/dist/redact.js +21 -0
- package/dist/remote-agents.d.ts +410 -0
- package/dist/remote-agents.js +1218 -0
- package/dist/remote-hook.d.ts +12 -0
- package/dist/remote-hook.js +196 -0
- package/dist/replace-file.d.ts +50 -0
- package/dist/replace-file.js +328 -0
- package/dist/schedule-command.d.ts +35 -0
- package/dist/schedule-command.js +260 -0
- package/dist/schedule-launch.d.ts +15 -0
- package/dist/schedule-launch.js +24 -0
- package/dist/schedule-ready.d.ts +44 -0
- package/dist/schedule-ready.js +71 -0
- package/dist/schedule-remote.d.ts +77 -0
- package/dist/schedule-remote.js +167 -0
- package/dist/schedule-runner.d.ts +89 -0
- package/dist/schedule-runner.js +452 -0
- package/dist/schedule.d.ts +149 -0
- package/dist/schedule.js +428 -0
- package/dist/secret-mask.d.ts +29 -0
- package/dist/secret-mask.js +245 -0
- package/dist/self-register.d.ts +45 -0
- package/dist/self-register.js +67 -0
- package/dist/send.d.ts +56 -0
- package/dist/send.js +104 -0
- package/dist/session-diff.d.ts +161 -0
- package/dist/session-diff.js +445 -0
- package/dist/session-registry.d.ts +143 -0
- package/dist/session-registry.js +216 -0
- package/dist/sessions-report.d.ts +59 -0
- package/dist/sessions-report.js +99 -0
- package/dist/shell-audit.d.ts +34 -0
- package/dist/shell-audit.js +47 -0
- package/dist/subagent-transcripts.d.ts +167 -0
- package/dist/subagent-transcripts.js +437 -0
- package/dist/subscriber-trust.d.ts +128 -0
- package/dist/subscriber-trust.js +397 -0
- package/dist/templates.d.ts +105 -0
- package/dist/templates.js +871 -0
- package/dist/tmux-install.d.ts +44 -0
- package/dist/tmux-install.js +65 -0
- package/dist/tmux-liveness.d.ts +45 -0
- package/dist/tmux-liveness.js +71 -0
- package/dist/tmux-pane.d.ts +55 -0
- package/dist/tmux-pane.js +126 -0
- package/dist/tmux-socket.d.ts +20 -0
- package/dist/tmux-socket.js +292 -0
- package/dist/todo-fold.d.ts +31 -0
- package/dist/todo-fold.js +102 -0
- package/dist/transcript-tail.d.ts +312 -0
- package/dist/transcript-tail.js +718 -0
- package/dist/trust-command.d.ts +49 -0
- package/dist/trust-command.js +200 -0
- package/dist/trust-lock.d.ts +26 -0
- package/dist/trust-lock.js +94 -0
- package/dist/trust-pins.d.ts +37 -0
- package/dist/trust-pins.js +82 -0
- package/dist/turn-recorder-wire.d.ts +70 -0
- package/dist/turn-recorder-wire.js +165 -0
- package/dist/turn-recorder.d.ts +190 -0
- package/dist/turn-recorder.js +695 -0
- package/dist/turn-ring.d.ts +93 -0
- package/dist/turn-ring.js +418 -0
- package/dist/turn-watch.d.ts +177 -0
- package/dist/turn-watch.js +630 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.js +2 -0
- package/dist/uninstall.d.ts +55 -0
- package/dist/uninstall.js +507 -0
- package/dist/usage.d.ts +17 -0
- package/dist/usage.js +421 -0
- package/dist/verify.d.ts +47 -0
- package/dist/verify.js +396 -0
- package/dist/worktree.d.ts +144 -0
- package/dist/worktree.js +310 -0
- package/dist/wrapper.d.ts +254 -0
- package/dist/wrapper.js +704 -0
- package/dist/ws-url.d.ts +109 -0
- package/dist/ws-url.js +202 -0
- package/package.json +78 -10
- package/bin/hwip.js +0 -3
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface HwipOptions {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface NotifyPayload {
|
|
7
|
+
title?: string;
|
|
8
|
+
body?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
type?: 'note' | 'link' | 'file' | 'hook';
|
|
11
|
+
priority?: 'low' | 'normal' | 'high' | 'urgent';
|
|
12
|
+
targetDeviceId?: string;
|
|
13
|
+
/** Send to this channel's subscribers instead of to the account's own devices. Plain text — see `HwipHook.notify`. */
|
|
14
|
+
channelId?: string;
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
/** Stable agent-session grouping so a hook/notify joins the agent chat:
|
|
17
|
+
* the listener device (per-host) + tmux session name. Auto-filled by
|
|
18
|
+
* HwipHook.notify when running inside a tmux agent session. */
|
|
19
|
+
agentDeviceId?: string;
|
|
20
|
+
agentSessionName?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface NotifyResult {
|
|
23
|
+
pushId: string;
|
|
24
|
+
fileKey?: string;
|
|
25
|
+
autoFile?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface UploadRequestResult {
|
|
28
|
+
fileId: string;
|
|
29
|
+
fileKey: string;
|
|
30
|
+
uploadUrl: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ListParams {
|
|
33
|
+
limit?: number;
|
|
34
|
+
type?: 'note' | 'link' | 'file' | 'clipboard' | 'hook';
|
|
35
|
+
}
|
|
36
|
+
export interface PushItem {
|
|
37
|
+
pushId: string;
|
|
38
|
+
type: string;
|
|
39
|
+
title?: string;
|
|
40
|
+
body?: string;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
/** E2E: when true, `title`/`body` are absent and the ciphertext is in `body`. */
|
|
43
|
+
isEncrypted?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface ListResult {
|
|
46
|
+
pushes: PushItem[];
|
|
47
|
+
count: number;
|
|
48
|
+
hasMore: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface DismissOneResult {
|
|
51
|
+
dismissed: true;
|
|
52
|
+
}
|
|
53
|
+
export interface DismissAllResult {
|
|
54
|
+
dismissed: number;
|
|
55
|
+
}
|
|
56
|
+
export interface ApiErrorResponse {
|
|
57
|
+
error: {
|
|
58
|
+
code: string;
|
|
59
|
+
message: string;
|
|
60
|
+
status: number;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { checkListenerPid, type ListenerStop } from './listener-process.js';
|
|
2
|
+
/** A step line that is not a change made: a `note` is for information, an
|
|
3
|
+
* `attention` line is something only the user can finish. */
|
|
4
|
+
export type Notice = {
|
|
5
|
+
readonly notice: 'note' | 'attention';
|
|
6
|
+
readonly text: string;
|
|
7
|
+
};
|
|
8
|
+
type StepLine = string | Notice;
|
|
9
|
+
/** Remove just the `hwip` entry from an MCP registry JSON file. `key` is the
|
|
10
|
+
* container: `mcpServers` for Cursor/Windsurf, top-level `mcp` for
|
|
11
|
+
* opencode.json (opencode's own schema). Exported for tests. */
|
|
12
|
+
export declare const rmMcpEntry: (filePath: string, dry: boolean, key?: string) => StepLine | null;
|
|
13
|
+
/** Remove `[mcp_servers.hwip]` from codex's TOML config, leaving the rest of
|
|
14
|
+
* the user's file byte-for-byte. Exported for tests. */
|
|
15
|
+
export declare const rmCodexMcpEntry: (filePath: string, dry: boolean) => StepLine | null;
|
|
16
|
+
/** Strip the <!-- HWIP:START/END --> block from a shared rule file. Exported for tests. */
|
|
17
|
+
export declare const stripManagedRule: (filePath: string, dry: boolean) => string | null;
|
|
18
|
+
/** Drop the Hwip `read:` directive from ~/.aider.conf.yml. */
|
|
19
|
+
/** Exported for tests. */
|
|
20
|
+
export declare const rmAiderReadDirective: (confPath: string, dry: boolean) => string | null;
|
|
21
|
+
/** Remove just the hwip-written entries from Gemini's settings.json.
|
|
22
|
+
* Exported for tests (gemini is PATH-detected, so the full-uninstall
|
|
23
|
+
* suite can't reach this deterministically). */
|
|
24
|
+
export declare const rmGeminiHook: (filePath: string, dry: boolean) => StepLine | null;
|
|
25
|
+
/** Remove the hwip-written entries from a hooks.json (Codex, Cursor,
|
|
26
|
+
* Windsurf). The file is usually hwip-born, so it is deleted outright when
|
|
27
|
+
* nothing but hwip's entries and Cursor's `version` was in it — hooks and keys
|
|
28
|
+
* the user added survive. Exported for tests (codex is PATH-detected, same as
|
|
29
|
+
* gemini). */
|
|
30
|
+
export declare const rmHwipHooks: (filePath: string, dry: boolean) => StepLine | null;
|
|
31
|
+
/**
|
|
32
|
+
* Remove the login-time service (LaunchAgent or systemd user unit), if one is installed.
|
|
33
|
+
*
|
|
34
|
+
* Async, so it does not fit the sync `Step` shape the per-agent uninstallers
|
|
35
|
+
* use — the service manager has to be waited on. Returns the same "one line or null"
|
|
36
|
+
* contract those steps do.
|
|
37
|
+
*/
|
|
38
|
+
export declare const removeServiceStep: (dry: boolean) => Promise<StepLine | null>;
|
|
39
|
+
export interface ListenerStopDeps {
|
|
40
|
+
readonly runningListenerPid: () => number | null;
|
|
41
|
+
readonly checkListenerPid: typeof checkListenerPid;
|
|
42
|
+
readonly stopListener: (pid: number) => Promise<ListenerStop>;
|
|
43
|
+
}
|
|
44
|
+
export declare const defaultListenerStopDeps: ListenerStopDeps;
|
|
45
|
+
/**
|
|
46
|
+
* Stop the listener daemon. `hwip cc` starts it without the login-time
|
|
47
|
+
* service, so removing the service alone leaves it running — and it is what
|
|
48
|
+
* carries phone input into tmux. `stopListener` only stops a process whose
|
|
49
|
+
* command line is hwip's listener; this turns what it found into one line.
|
|
50
|
+
* Same "one line or null" contract as the removal primitives.
|
|
51
|
+
*/
|
|
52
|
+
export declare const stopListenerStep: (dry: boolean, deps?: ListenerStopDeps) => Promise<StepLine | null>;
|
|
53
|
+
export declare const handleUninstall: (args: Record<string, string | boolean>, listenerDeps?: ListenerStopDeps, confirm?: (message: string) => Promise<boolean>) => Promise<number>;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=uninstall.d.ts.map
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleUninstall = exports.stopListenerStep = exports.defaultListenerStopDeps = exports.removeServiceStep = exports.rmHwipHooks = exports.rmGeminiHook = exports.rmAiderReadDirective = exports.stripManagedRule = exports.rmCodexMcpEntry = exports.rmMcpEntry = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const agents_js_1 = require("./agents.js");
|
|
9
|
+
const listener_process_js_1 = require("./listener-process.js");
|
|
10
|
+
const listener_service_js_1 = require("./listener-service.js");
|
|
11
|
+
const templates_js_1 = require("./templates.js");
|
|
12
|
+
const config_js_1 = require("./config.js");
|
|
13
|
+
const codex_config_js_1 = require("./codex-config.js");
|
|
14
|
+
const replace_file_js_1 = require("./replace-file.js");
|
|
15
|
+
const jsonc_doc_js_1 = require("./jsonc-doc.js");
|
|
16
|
+
const args_js_1 = require("./args.js");
|
|
17
|
+
const HOME = (0, os_1.homedir)();
|
|
18
|
+
const ok = (msg) => console.log(` + ${msg}`);
|
|
19
|
+
const skip = (msg) => console.log(` - ${msg}`);
|
|
20
|
+
let attentionLines = 0;
|
|
21
|
+
const report = (line) => {
|
|
22
|
+
if (typeof line === 'string')
|
|
23
|
+
return ok(line);
|
|
24
|
+
if (line.notice === 'note')
|
|
25
|
+
return skip(line.text);
|
|
26
|
+
attentionLines += 1;
|
|
27
|
+
console.log(` ! ${line.text}`);
|
|
28
|
+
};
|
|
29
|
+
// ── Removal primitives ───────────────────────────────────────────
|
|
30
|
+
// Each primitive returns a short human description of what it did (or
|
|
31
|
+
// would do, in dry-run), or null when there was nothing to remove. A JSON
|
|
32
|
+
// file it could not read gets an `attention` line (`unreadable`), so the user
|
|
33
|
+
// knows to look at it: whatever of hwip's is in there stays and keeps working.
|
|
34
|
+
/** Past/conditional verb so dry-run output reads honestly. */
|
|
35
|
+
const verb = (dry) => (dry ? 'would remove' : 'removed');
|
|
36
|
+
/** Delete a file Hwip fully owns. */
|
|
37
|
+
const rmFile = (filePath, dry) => {
|
|
38
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
39
|
+
return null;
|
|
40
|
+
if (!dry)
|
|
41
|
+
(0, fs_1.rmSync)(filePath, { force: true });
|
|
42
|
+
return `${verb(dry)} ${filePath}`;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Is the file reached through a symlink — itself, or a folder on the way from the home folder (`~/.cursor`
|
|
46
|
+
* linked into a dotfiles repo)? Folders above the home folder are not looked at: a home folder under a
|
|
47
|
+
* linked parent (macOS's /var) says nothing about where the user keeps their files.
|
|
48
|
+
*/
|
|
49
|
+
const isLinked = (filePath) => {
|
|
50
|
+
const rel = (0, path_1.relative)(HOME, filePath);
|
|
51
|
+
const parts = rel.startsWith('..') || (0, path_1.isAbsolute)(rel) ? [''] : rel.split(path_1.sep);
|
|
52
|
+
let at = rel.startsWith('..') || (0, path_1.isAbsolute)(rel) ? filePath : HOME;
|
|
53
|
+
for (const part of parts) {
|
|
54
|
+
at = part ? (0, path_1.join)(at, part) : at;
|
|
55
|
+
if ((0, fs_1.lstatSync)(at, { throwIfNoEntry: false })?.isSymbolicLink() === true)
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
};
|
|
60
|
+
const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
61
|
+
/** A settings file opened as the installer opens it (comments and trailing
|
|
62
|
+
* commas allowed, an empty or BOM-led file read the same way), or null when it
|
|
63
|
+
* does not parse or parses to something else (`null`, an array) that has no
|
|
64
|
+
* entries to look in. */
|
|
65
|
+
const openSettings = (filePath) => {
|
|
66
|
+
let text;
|
|
67
|
+
try {
|
|
68
|
+
text = (0, replace_file_js_1.readText)(filePath);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
// Bytes that are not text are named as that (the step runner prints the throw), not as "not a JSON object".
|
|
72
|
+
if (err instanceof replace_file_js_1.NotUtf8Error)
|
|
73
|
+
throw err;
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
return new jsonc_doc_js_1.JsoncDoc(text);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
/** The `!` line for an edit that would lose a comment of the user's: the file is left as it is. */
|
|
84
|
+
const uneditable = (filePath, what) => ({
|
|
85
|
+
notice: 'attention',
|
|
86
|
+
text: `could not edit ${filePath} without risking its comments — remove hwip's ${what} by hand`,
|
|
87
|
+
});
|
|
88
|
+
/** Run an edit of a settings file; a `JsoncEditError` becomes the `!` line that names the file. */
|
|
89
|
+
const editing = (filePath, what, edit) => {
|
|
90
|
+
try {
|
|
91
|
+
return edit();
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err instanceof jsonc_doc_js_1.JsoncEditError)
|
|
95
|
+
return uneditable(filePath, what);
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
/** The `!` line for a settings file that exists but is not a JSON object. */
|
|
100
|
+
const unreadable = (filePath, what) => ({
|
|
101
|
+
notice: 'attention',
|
|
102
|
+
text: `could not read ${filePath} as a JSON object — if hwip's ${what} is in it, remove it by hand`,
|
|
103
|
+
});
|
|
104
|
+
/** Remove just the `hwip` entry from an MCP registry JSON file. `key` is the
|
|
105
|
+
* container: `mcpServers` for Cursor/Windsurf, top-level `mcp` for
|
|
106
|
+
* opencode.json (opencode's own schema). Exported for tests. */
|
|
107
|
+
const rmMcpEntry = (filePath, dry, key = 'mcpServers') => {
|
|
108
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
109
|
+
return null;
|
|
110
|
+
const doc = openSettings(filePath);
|
|
111
|
+
if (!doc)
|
|
112
|
+
return unreadable(filePath, 'MCP entry');
|
|
113
|
+
const servers = doc.data[key];
|
|
114
|
+
if (!isPlainObject(servers) || !('hwip' in servers))
|
|
115
|
+
return null;
|
|
116
|
+
return editing(filePath, 'MCP entry', () => {
|
|
117
|
+
if (!dry) {
|
|
118
|
+
doc.remove([key, 'hwip']);
|
|
119
|
+
(0, replace_file_js_1.replaceFile)(filePath, doc.text(), doc.expected);
|
|
120
|
+
}
|
|
121
|
+
return `${verb(dry)} hwip from ${filePath}`;
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
exports.rmMcpEntry = rmMcpEntry;
|
|
125
|
+
/** Remove `[mcp_servers.hwip]` from codex's TOML config, leaving the rest of
|
|
126
|
+
* the user's file byte-for-byte. Exported for tests. */
|
|
127
|
+
const rmCodexMcpEntry = (filePath, dry) => {
|
|
128
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
129
|
+
return null;
|
|
130
|
+
let config;
|
|
131
|
+
try {
|
|
132
|
+
config = (0, replace_file_js_1.readText)(filePath);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
// A table left in a file hwip cannot read keeps launching its server: say so.
|
|
136
|
+
if (err instanceof replace_file_js_1.NotUtf8Error)
|
|
137
|
+
return { notice: 'attention', text: `${err.message} — if hwip's MCP table is in it, remove it by hand` };
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const stripped = (0, codex_config_js_1.removeHwipMcpTable)(config);
|
|
141
|
+
if (stripped === null)
|
|
142
|
+
return null;
|
|
143
|
+
if (!dry)
|
|
144
|
+
(0, replace_file_js_1.replaceFile)(filePath, stripped, config);
|
|
145
|
+
return `${verb(dry)} hwip from ${filePath}`;
|
|
146
|
+
};
|
|
147
|
+
exports.rmCodexMcpEntry = rmCodexMcpEntry;
|
|
148
|
+
/** Strip the <!-- HWIP:START/END --> block from a shared rule file. Exported for tests. */
|
|
149
|
+
const stripManagedRule = (filePath, dry) => {
|
|
150
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
151
|
+
return null;
|
|
152
|
+
const existing = (0, replace_file_js_1.readText)(filePath);
|
|
153
|
+
const stripped = (0, templates_js_1.removeManagedBlock)(existing);
|
|
154
|
+
if (stripped === existing)
|
|
155
|
+
return null; // no Hwip block present
|
|
156
|
+
if (!dry) {
|
|
157
|
+
// A file that was ours alone goes — but a symlink into a dotfiles repo is not ours to delete: the block comes out of what it points at.
|
|
158
|
+
if (stripped.trim() === '' && !isLinked(filePath)) {
|
|
159
|
+
(0, replace_file_js_1.removeFile)(filePath, existing);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
(0, replace_file_js_1.replaceFile)(filePath, stripped, existing);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return `${verb(dry)} Hwip block from ${filePath}`;
|
|
166
|
+
};
|
|
167
|
+
exports.stripManagedRule = stripManagedRule;
|
|
168
|
+
/** Drop the Hwip `read:` directive from ~/.aider.conf.yml. */
|
|
169
|
+
/** Exported for tests. */
|
|
170
|
+
const rmAiderReadDirective = (confPath, dry) => {
|
|
171
|
+
if (!(0, fs_1.existsSync)(confPath))
|
|
172
|
+
return null;
|
|
173
|
+
const conf = (0, replace_file_js_1.readText)(confPath);
|
|
174
|
+
if (!conf.includes('# Added by Hwip'))
|
|
175
|
+
return null;
|
|
176
|
+
// Drop the "# Added by Hwip" line and the "read:" line that follows it.
|
|
177
|
+
const lines = conf.split('\n');
|
|
178
|
+
const out = [];
|
|
179
|
+
for (let i = 0; i < lines.length; i++) {
|
|
180
|
+
if (lines[i].trim() === '# Added by Hwip') {
|
|
181
|
+
if (lines[i + 1]?.trimStart().startsWith('read:'))
|
|
182
|
+
i++; // skip read: too
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
out.push(lines[i]);
|
|
186
|
+
}
|
|
187
|
+
if (!dry)
|
|
188
|
+
(0, replace_file_js_1.replaceFile)(confPath, out.join('\n').replace(/\n{3,}/g, '\n\n'), conf);
|
|
189
|
+
return `${verb(dry)} Hwip read: directive from ${confPath}`;
|
|
190
|
+
};
|
|
191
|
+
exports.rmAiderReadDirective = rmAiderReadDirective;
|
|
192
|
+
/**
|
|
193
|
+
* Take hwip-written entries — matcher groups or flat handlers — out of the
|
|
194
|
+
* document's `hooks` (all events).
|
|
195
|
+
* Edits `doc` unless dry; returns true when anything of ours was found.
|
|
196
|
+
* Empty events are deleted so a clean file stays clean (an event that holds a
|
|
197
|
+
* comment of the user's stays, emptied). The rest of the file,
|
|
198
|
+
* comments included, is left as it is (a comment right next to one of hwip's
|
|
199
|
+
* entries goes with it).
|
|
200
|
+
*/
|
|
201
|
+
const stripHwipHookGroups = (doc, dry) => {
|
|
202
|
+
const hooks = doc.data.hooks;
|
|
203
|
+
if (!isPlainObject(hooks))
|
|
204
|
+
return false;
|
|
205
|
+
let removed = false;
|
|
206
|
+
for (const event of Object.keys(hooks)) {
|
|
207
|
+
const groups = hooks[event];
|
|
208
|
+
if (!Array.isArray(groups))
|
|
209
|
+
continue;
|
|
210
|
+
const ours = groups.flatMap((group, i) => ((0, templates_js_1.isHwipHookGroup)(group) ? [i] : []));
|
|
211
|
+
if (ours.length === 0)
|
|
212
|
+
continue; // nothing of ours
|
|
213
|
+
removed = true;
|
|
214
|
+
if (dry)
|
|
215
|
+
continue;
|
|
216
|
+
// The whole event goes only when that takes no comment with it; otherwise its groups go one by one and the key stays.
|
|
217
|
+
if (ours.length === groups.length && !doc.removalTakesComment(['hooks', event]))
|
|
218
|
+
doc.remove(['hooks', event]);
|
|
219
|
+
else
|
|
220
|
+
for (const i of ours.reverse())
|
|
221
|
+
doc.remove(['hooks', event, i]);
|
|
222
|
+
}
|
|
223
|
+
return removed;
|
|
224
|
+
};
|
|
225
|
+
/** Remove just the hwip-written entries from Gemini's settings.json.
|
|
226
|
+
* Exported for tests (gemini is PATH-detected, so the full-uninstall
|
|
227
|
+
* suite can't reach this deterministically). */
|
|
228
|
+
const rmGeminiHook = (filePath, dry) => {
|
|
229
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
230
|
+
return null;
|
|
231
|
+
const doc = openSettings(filePath);
|
|
232
|
+
if (!doc)
|
|
233
|
+
return unreadable(filePath, 'hook');
|
|
234
|
+
return editing(filePath, 'hook', () => {
|
|
235
|
+
if (!stripHwipHookGroups(doc, dry))
|
|
236
|
+
return null;
|
|
237
|
+
if (!dry)
|
|
238
|
+
(0, replace_file_js_1.replaceFile)(filePath, doc.text(), doc.expected);
|
|
239
|
+
return `${verb(dry)} hwip hooks from ${filePath}`;
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
exports.rmGeminiHook = rmGeminiHook;
|
|
243
|
+
/** Remove the hwip-written entries from a hooks.json (Codex, Cursor,
|
|
244
|
+
* Windsurf). The file is usually hwip-born, so it is deleted outright when
|
|
245
|
+
* nothing but hwip's entries and Cursor's `version` was in it — hooks and keys
|
|
246
|
+
* the user added survive. Exported for tests (codex is PATH-detected, same as
|
|
247
|
+
* gemini). */
|
|
248
|
+
const rmHwipHooks = (filePath, dry) => {
|
|
249
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
250
|
+
return null;
|
|
251
|
+
const doc = openSettings(filePath);
|
|
252
|
+
// Not silent: a hook left in an unreadable file keeps firing after
|
|
253
|
+
// uninstall, and the user is the only one who can take it out.
|
|
254
|
+
if (!doc)
|
|
255
|
+
return unreadable(filePath, 'hook');
|
|
256
|
+
return editing(filePath, 'hook', () => {
|
|
257
|
+
if (!stripHwipHookGroups(doc, dry))
|
|
258
|
+
return null;
|
|
259
|
+
if (!dry) {
|
|
260
|
+
const data = doc.data;
|
|
261
|
+
const hooks = isPlainObject(data.hooks) ? data.hooks : {};
|
|
262
|
+
const userKeys = Object.keys(data).filter((key) => key !== 'hooks' && key !== 'version');
|
|
263
|
+
// A comment is the user's too: a file that holds one is kept.
|
|
264
|
+
const onlyHwip = Object.keys(hooks).length === 0 && userKeys.length === 0 && !doc.hasComments();
|
|
265
|
+
// A symlink into a dotfiles repo is not ours to delete: hwip's hooks come out of what it points at,
|
|
266
|
+
// or they come back, and run, when the link is made again.
|
|
267
|
+
if (onlyHwip && !isLinked(filePath) && doc.expected !== null)
|
|
268
|
+
(0, replace_file_js_1.removeFile)(filePath, doc.expected);
|
|
269
|
+
else
|
|
270
|
+
(0, replace_file_js_1.replaceFile)(filePath, doc.text(), doc.expected);
|
|
271
|
+
}
|
|
272
|
+
return `${verb(dry)} hwip hooks from ${filePath}`;
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
exports.rmHwipHooks = rmHwipHooks;
|
|
276
|
+
// A step that throws (a write the directory refuses) is one line, not the end
|
|
277
|
+
// of the uninstall: the steps after it still have hwip's parts to take out.
|
|
278
|
+
const runStep = (step) => {
|
|
279
|
+
try {
|
|
280
|
+
return step();
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
return { notice: 'attention', text: `could not finish: ${err instanceof Error ? err.message : String(err)}` };
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const runSteps = (steps) => {
|
|
287
|
+
let did = false;
|
|
288
|
+
for (const step of steps) {
|
|
289
|
+
const result = runStep(step);
|
|
290
|
+
if (result) {
|
|
291
|
+
report(result);
|
|
292
|
+
did = true;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (!did)
|
|
296
|
+
skip('nothing to remove');
|
|
297
|
+
};
|
|
298
|
+
const AGENT_UNINSTALLERS = {
|
|
299
|
+
claude: (dry) => {
|
|
300
|
+
if (dry) {
|
|
301
|
+
skip('would run: claude plugin uninstall hwip@hwip');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
(0, child_process_1.execSync)('claude plugin uninstall hwip@hwip', { stdio: 'pipe' });
|
|
306
|
+
ok('plugin uninstalled');
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
skip('plugin not installed (or claude CLI unavailable)');
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
cursor: (dry) => runSteps([
|
|
313
|
+
() => (0, exports.rmMcpEntry)((0, path_1.join)(HOME, '.cursor', 'mcp.json'), dry),
|
|
314
|
+
() => (0, exports.rmHwipHooks)((0, path_1.join)(HOME, '.cursor', 'hooks.json'), dry),
|
|
315
|
+
() => rmFile((0, path_1.join)(HOME, '.cursor', 'rules', 'hwip.mdc'), dry),
|
|
316
|
+
]),
|
|
317
|
+
windsurf: (dry) => runSteps([
|
|
318
|
+
() => (0, exports.rmMcpEntry)((0, path_1.join)(HOME, '.codeium', 'windsurf', 'mcp_config.json'), dry),
|
|
319
|
+
() => (0, exports.rmHwipHooks)((0, path_1.join)(HOME, '.codeium', 'windsurf', 'hooks.json'), dry),
|
|
320
|
+
() => (0, exports.stripManagedRule)((0, path_1.join)(HOME, '.codeium', 'windsurf', 'memories', 'global_rules.md'), dry),
|
|
321
|
+
]),
|
|
322
|
+
gemini: (dry) => {
|
|
323
|
+
if (!dry) {
|
|
324
|
+
// user scope first (where the ≥0.26 installer adds it), then the
|
|
325
|
+
// legacy scope-less form for entries created by older installs.
|
|
326
|
+
try {
|
|
327
|
+
(0, child_process_1.execSync)('gemini mcp remove -s user hwip', { stdio: 'pipe' });
|
|
328
|
+
ok('MCP server removed');
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
try {
|
|
332
|
+
(0, child_process_1.execSync)('gemini mcp remove hwip', { stdio: 'pipe' });
|
|
333
|
+
ok('MCP server removed');
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
skip('gemini MCP entry not found');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
skip('would run: gemini mcp remove -s user hwip');
|
|
342
|
+
}
|
|
343
|
+
runSteps([
|
|
344
|
+
() => (0, exports.rmGeminiHook)((0, path_1.join)(HOME, '.gemini', 'settings.json'), dry),
|
|
345
|
+
() => (0, exports.stripManagedRule)((0, path_1.join)(HOME, '.gemini', 'GEMINI.md'), dry),
|
|
346
|
+
]);
|
|
347
|
+
},
|
|
348
|
+
codex: (dry) => runSteps([
|
|
349
|
+
() => (0, exports.rmHwipHooks)((0, path_1.join)(HOME, '.codex', 'hooks.json'), dry),
|
|
350
|
+
() => (0, exports.stripManagedRule)((0, path_1.join)(HOME, '.codex', 'AGENTS.md'), dry),
|
|
351
|
+
() => (0, exports.rmCodexMcpEntry)((0, path_1.join)(HOME, '.codex', 'config.toml'), dry),
|
|
352
|
+
]),
|
|
353
|
+
copilot: (dry) => runSteps([
|
|
354
|
+
() => rmFile((0, path_1.join)(HOME, '.copilot', 'hooks', 'hwip.json'), dry),
|
|
355
|
+
() => rmFile((0, path_1.join)(HOME, '.copilot', 'instructions', 'hwip.instructions.md'), dry),
|
|
356
|
+
]),
|
|
357
|
+
cline: (dry) => runSteps([
|
|
358
|
+
() => rmFile((0, path_1.join)(HOME, '.cline', 'rules', 'hwip.md'), dry),
|
|
359
|
+
]),
|
|
360
|
+
aider: (dry) => runSteps([
|
|
361
|
+
() => rmFile((0, path_1.join)(HOME, '.hwip', 'aider-conventions.md'), dry),
|
|
362
|
+
() => (0, exports.rmAiderReadDirective)((0, path_1.join)(HOME, '.aider.conf.yml'), dry),
|
|
363
|
+
]),
|
|
364
|
+
pi: (dry) => runSteps([
|
|
365
|
+
() => rmFile((0, path_1.join)(HOME, '.pi', 'agent', 'extensions', 'hwip.ts'), dry),
|
|
366
|
+
() => (0, exports.stripManagedRule)((0, path_1.join)(HOME, '.pi', 'agent', 'AGENTS.md'), dry),
|
|
367
|
+
]),
|
|
368
|
+
opencode: (dry) => runSteps([
|
|
369
|
+
() => (0, exports.rmMcpEntry)((0, path_1.join)(HOME, '.config', 'opencode', 'opencode.json'), dry, 'mcp'),
|
|
370
|
+
() => rmFile((0, path_1.join)(HOME, '.config', 'opencode', 'plugins', 'hwip.ts'), dry),
|
|
371
|
+
() => (0, exports.stripManagedRule)((0, path_1.join)(HOME, '.config', 'opencode', 'AGENTS.md'), dry),
|
|
372
|
+
]),
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* Remove the login-time service (LaunchAgent or systemd user unit), if one is installed.
|
|
376
|
+
*
|
|
377
|
+
* Async, so it does not fit the sync `Step` shape the per-agent uninstallers
|
|
378
|
+
* use — the service manager has to be waited on. Returns the same "one line or null"
|
|
379
|
+
* contract those steps do.
|
|
380
|
+
*/
|
|
381
|
+
const removeServiceStep = async (dry) => {
|
|
382
|
+
if (!(0, listener_service_js_1.serviceInstalled)())
|
|
383
|
+
return null;
|
|
384
|
+
if (dry)
|
|
385
|
+
return `would remove the login-time service (${listener_service_js_1.SERVICE_LABEL})`;
|
|
386
|
+
const result = await (0, listener_service_js_1.uninstallService)();
|
|
387
|
+
if (!result.ok)
|
|
388
|
+
return { notice: 'attention', text: `could not remove the login-time service: ${result.reason}` };
|
|
389
|
+
return `removed the login-time service (${listener_service_js_1.SERVICE_LABEL})`;
|
|
390
|
+
};
|
|
391
|
+
exports.removeServiceStep = removeServiceStep;
|
|
392
|
+
exports.defaultListenerStopDeps = {
|
|
393
|
+
runningListenerPid: listener_process_js_1.runningListenerPid,
|
|
394
|
+
checkListenerPid: (pid) => (0, listener_process_js_1.checkListenerPid)(pid),
|
|
395
|
+
stopListener: (pid) => (0, listener_process_js_1.stopListener)(pid),
|
|
396
|
+
};
|
|
397
|
+
/**
|
|
398
|
+
* Stop the listener daemon. `hwip cc` starts it without the login-time
|
|
399
|
+
* service, so removing the service alone leaves it running — and it is what
|
|
400
|
+
* carries phone input into tmux. `stopListener` only stops a process whose
|
|
401
|
+
* command line is hwip's listener; this turns what it found into one line.
|
|
402
|
+
* Same "one line or null" contract as the removal primitives.
|
|
403
|
+
*/
|
|
404
|
+
const stopListenerStep = async (dry, deps = exports.defaultListenerStopDeps) => {
|
|
405
|
+
const pid = deps.runningListenerPid();
|
|
406
|
+
if (pid === null)
|
|
407
|
+
return null;
|
|
408
|
+
const outcome = dry ? deps.checkListenerPid(pid) : await deps.stopListener(pid);
|
|
409
|
+
switch (outcome.kind) {
|
|
410
|
+
case 'gone':
|
|
411
|
+
return null;
|
|
412
|
+
case 'unchecked':
|
|
413
|
+
return {
|
|
414
|
+
notice: 'attention',
|
|
415
|
+
text: `could not check pid ${pid} (${outcome.reason}) — if it is the listener, run: hwip listener --stop`,
|
|
416
|
+
};
|
|
417
|
+
case 'foreign':
|
|
418
|
+
return { notice: 'note', text: `left pid ${pid} alone: the listener's pid file points at another process (${outcome.program})` };
|
|
419
|
+
case 'other-listener':
|
|
420
|
+
return { notice: 'attention', text: (0, listener_process_js_1.otherListenerNote)(pid, outcome.script) };
|
|
421
|
+
case 'ours':
|
|
422
|
+
return `would stop the listener (pid ${pid})`;
|
|
423
|
+
case 'stopped':
|
|
424
|
+
return `stopped the listener (pid ${pid})`;
|
|
425
|
+
case 'killed':
|
|
426
|
+
return `stopped the listener (pid ${pid}) with SIGKILL`;
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
exports.stopListenerStep = stopListenerStep;
|
|
430
|
+
// ── Entry point ──────────────────────────────────────────────────
|
|
431
|
+
const closingLine = (dry, attention) => {
|
|
432
|
+
const lines = attention === 1 ? '1 line marked "!" needs' : `${attention} lines marked "!" need`;
|
|
433
|
+
if (dry) {
|
|
434
|
+
const tail = attention === 0 ? '' : ` ${lines} you.`;
|
|
435
|
+
return `\n Dry-run complete — nothing was changed.${tail}\n`;
|
|
436
|
+
}
|
|
437
|
+
if (attention === 0)
|
|
438
|
+
return '\n Uninstall complete. Restart your agents.\n';
|
|
439
|
+
return `\n Uninstall finished, but ${lines} you. Then restart your agents.\n`;
|
|
440
|
+
};
|
|
441
|
+
/** Yes/no at the terminal; Ctrl-C is a no. */
|
|
442
|
+
const askConfirm = async (message) => {
|
|
443
|
+
try {
|
|
444
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
445
|
+
return await confirm({ message, default: false });
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
const handleUninstall = async (args, listenerDeps = exports.defaultListenerStopDeps, confirm = askConfirm) => {
|
|
452
|
+
const dry = (0, args_js_1.flagOn)(args, 'dry-run');
|
|
453
|
+
const purge = (0, args_js_1.flagOn)(args, 'purge');
|
|
454
|
+
attentionLines = 0;
|
|
455
|
+
console.log(`\n Hwip uninstall${dry ? ' (dry-run)' : ''} — v${config_js_1.VERSION}\n`);
|
|
456
|
+
const detected = (0, agents_js_1.detectAgents)().filter((a) => a.detected);
|
|
457
|
+
// A real removal asks first when someone is at the terminal. `--yes`
|
|
458
|
+
// skips the question; a script has no terminal to ask on and runs as it
|
|
459
|
+
// always did. `--dry-run` changes nothing, so it never asks.
|
|
460
|
+
if (!dry && !(0, args_js_1.flagOn)(args, 'yes') && process.stdin.isTTY === true) {
|
|
461
|
+
const agents = detected.map((a) => a.name).join(', ') || 'no detected agents';
|
|
462
|
+
const go = await confirm(`Remove Hwip from ${agents} and stop the listener? (--dry-run previews it)`);
|
|
463
|
+
if (!go) {
|
|
464
|
+
console.log('\n Cancelled — nothing was changed.\n');
|
|
465
|
+
// Not 0: a caller can tell "removed" from "said no".
|
|
466
|
+
return 1;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (detected.length === 0) {
|
|
470
|
+
console.log(' No supported agents detected.\n');
|
|
471
|
+
}
|
|
472
|
+
for (const agent of detected) {
|
|
473
|
+
console.log(` ${agent.name}:`);
|
|
474
|
+
AGENT_UNINSTALLERS[agent.id]?.(dry);
|
|
475
|
+
}
|
|
476
|
+
// The LaunchAgent outlives every agent's rules, so removing it is part of
|
|
477
|
+
// uninstall and not of any one agent's teardown.
|
|
478
|
+
console.log('\n Login-time service:');
|
|
479
|
+
const serviceLine = await (0, exports.removeServiceStep)(dry);
|
|
480
|
+
if (serviceLine)
|
|
481
|
+
report(serviceLine);
|
|
482
|
+
else
|
|
483
|
+
skip('not installed');
|
|
484
|
+
// After the service: its bootout already stops a service-run daemon.
|
|
485
|
+
console.log('\n Listener:');
|
|
486
|
+
const listenerLine = await (0, exports.stopListenerStep)(dry, listenerDeps);
|
|
487
|
+
if (listenerLine)
|
|
488
|
+
report(listenerLine);
|
|
489
|
+
else
|
|
490
|
+
skip('not running');
|
|
491
|
+
// ~/.hwip/config.json holds the API key — kept by default so a
|
|
492
|
+
// re-install doesn't need the key re-entered. --purge removes it.
|
|
493
|
+
console.log('\n Config:');
|
|
494
|
+
if (purge) {
|
|
495
|
+
const removed = rmFile(config_js_1.CONFIG_FILE, dry);
|
|
496
|
+
if (removed)
|
|
497
|
+
ok(removed);
|
|
498
|
+
else
|
|
499
|
+
skip('no config file');
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
skip(`kept ${config_js_1.CONFIG_FILE} (pass --purge to remove)`);
|
|
503
|
+
}
|
|
504
|
+
console.log(closingLine(dry, attentionLines));
|
|
505
|
+
return 0;
|
|
506
|
+
};
|
|
507
|
+
exports.handleUninstall = handleUninstall;
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const usageText: () => string;
|
|
2
|
+
/**
|
|
3
|
+
* The short usage of one command: its entry from the full text, its options
|
|
4
|
+
* section if it has one, and the global options. Null for a word that is not a
|
|
5
|
+
* command, which the caller answers with the full usage.
|
|
6
|
+
*
|
|
7
|
+
* Every agent subcommand (`cc`, `codex`, …) shares the paragraph the full text
|
|
8
|
+
* prints once under the last of them, so each gets it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const commandUsage: (command: string) => string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Which command a request for usage is about: `hwip notify --help` and
|
|
13
|
+
* `hwip --help cc` name it, `hwip help notify` names it as its argument, and
|
|
14
|
+
* `hwip` or `hwip help` alone name none.
|
|
15
|
+
*/
|
|
16
|
+
export declare const usageTarget: (args: Readonly<Record<string, string | boolean>>) => string;
|
|
17
|
+
//# sourceMappingURL=usage.d.ts.map
|