@porte/cli 0.1.1 → 0.1.3
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
|
@@ -1,25 +1,78 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Porte
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Porte - means door in French - is a secure remote interface for your local Grok conversations.
|
|
4
|
+
|
|
5
|
+
Manage the same Grok threads and repos from your phone while they continue to run on your laptop. Porte does not replace the TUI, and each account can access only its own machines.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```sh
|
|
8
10
|
npm i -g @porte/cli
|
|
11
|
+
porte pair
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Or hand it to your agent: `Fetch and execute the instructions from https://useporte.dev/agent-setup/prompt.md to set up Porte on this machine.`
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
1. Run the daemon on the machine where you already use Grok.
|
|
19
|
+
2. Sign in on your phone and pair that machine.
|
|
20
|
+
3. Open a conversation — or start a new one in a known repo.
|
|
21
|
+
4. Read the transcript. Prompt. Approve or deny.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Phone (your account) → Porte → your paired machine
|
|
25
|
+
│
|
|
26
|
+
Grok agent
|
|
27
|
+
│
|
|
28
|
+
local conversations + repos
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## What Porte handles
|
|
32
|
+
|
|
33
|
+
Porte relays remote actions while your machine keeps the Grok runtime and all local data.
|
|
34
|
+
|
|
35
|
+
| ☁️ Us | 💻 Your machine |
|
|
36
|
+
| -------------------------------------- | ------------------------- |
|
|
37
|
+
| App account + pairing | `grok.com` login, spend |
|
|
38
|
+
| Conversation titles (id, cwd, updated) | Repos, files, transcripts |
|
|
39
|
+
| Live relay of prompts and approvals | The Grok process |
|
|
40
|
+
|
|
41
|
+
## Safety
|
|
42
|
+
|
|
43
|
+
Remote Grok is the same Grok you already run.
|
|
44
|
+
|
|
45
|
+
- Starts in **that project’s directory**
|
|
46
|
+
- Inherits your `AGENTS.md`, rules, hooks, sandbox, and ask/deny
|
|
47
|
+
- No `--always-approve`. No extra filesystem or network power
|
|
48
|
+
- A click on the TUI is still a click on the phone. Deny still denies
|
|
49
|
+
|
|
50
|
+
## Development
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
pnpm install
|
|
54
|
+
pnpm dev # web on :3000; `pnpm dev up` connects a local host through the tunnel
|
|
55
|
+
pnpm turbo run lint typecheck test:unit test:integration
|
|
56
|
+
pnpm --filter @porte/web test:design # Playwright pictures; Mac only, run before a commit
|
|
9
57
|
```
|
|
10
58
|
|
|
11
|
-
|
|
59
|
+
Cloudflare types are generated (`worker-configuration.d.ts`, gitignored). Turbo runs `cf-typegen` before lint, typecheck, tests, and build, so a fresh checkout sees what a dev does.
|
|
60
|
+
|
|
61
|
+
### Releasing the CLI
|
|
12
62
|
|
|
13
|
-
|
|
63
|
+
`apps/host` publishes as `@porte/cli`. A release is a version bump on `main`:
|
|
14
64
|
|
|
15
65
|
```sh
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
porte unpair # end the pairing
|
|
66
|
+
pnpm --filter @porte/cli version patch --no-git-checks # bumps apps/host/package.json only
|
|
67
|
+
git commit -am "release(cli): x.y.z" && git push
|
|
19
68
|
```
|
|
20
69
|
|
|
21
|
-
|
|
70
|
+
`.github/workflows/publish-cli.yaml` lints, typechecks, tests, builds, and publishes with provenance through npm trusted publishing (`pnpm publish --provenance`, no token). A push that leaves the version alone publishes nothing. The npm page shows this README, copied at pack time.
|
|
71
|
+
|
|
72
|
+
### Deploying the web app
|
|
73
|
+
|
|
74
|
+
Cloudflare Workers Builds is connected to this repository: every push to `main` builds and deploys the web app. Nothing is deployed by hand.
|
|
22
75
|
|
|
23
|
-
|
|
76
|
+
## License
|
|
24
77
|
|
|
25
|
-
|
|
78
|
+
[Apache License 2.0](LICENSE).
|
|
@@ -5058,7 +5058,13 @@ const ConversationFailurePayloadSchema = discriminatedUnion("_tag", [
|
|
|
5058
5058
|
message: string().min(1)
|
|
5059
5059
|
})
|
|
5060
5060
|
]);
|
|
5061
|
-
|
|
5061
|
+
//#endregion
|
|
5062
|
+
//#region ../../packages/core/src/identity/identity.ts
|
|
5063
|
+
/**
|
|
5064
|
+
* These schemas define identifiers that cross Porte process boundaries.
|
|
5065
|
+
* Each brand prevents accidental use of a different identifier.
|
|
5066
|
+
*/
|
|
5067
|
+
const HostIdSchema = uuidv7().brand();
|
|
5062
5068
|
uuidv7().brand();
|
|
5063
5069
|
uuidv7().brand();
|
|
5064
5070
|
uuidv7().brand();
|
|
@@ -5640,11 +5646,11 @@ var ConfigurationNotFoundError = class extends h(CONFIGURATION_NOT_FOUND_ERROR)
|
|
|
5640
5646
|
const HOST_OFFLINE_ERROR = "HostOfflineError";
|
|
5641
5647
|
const HOST_ALREADY_PAIRED_ERROR = "HostAlreadyPairedError";
|
|
5642
5648
|
const WORKSPACE_NOT_ALLOWED_ERROR = "WorkspaceNotAllowedError";
|
|
5643
|
-
/** The path is not one the
|
|
5649
|
+
/** The path is not one the machine reported, so the daemon will not open it. */
|
|
5644
5650
|
var WorkspaceNotAllowedError = class extends h(WORKSPACE_NOT_ALLOWED_ERROR) {
|
|
5645
5651
|
constructor() {
|
|
5646
5652
|
super({
|
|
5647
|
-
message: "That repository is not available on this
|
|
5653
|
+
message: "That repository is not available on this machine",
|
|
5648
5654
|
classification: "terminal"
|
|
5649
5655
|
});
|
|
5650
5656
|
}
|
|
@@ -5778,17 +5784,17 @@ const HostPlatformSchema = _enum([
|
|
|
5778
5784
|
//#endregion
|
|
5779
5785
|
//#region ../../packages/core/src/host/account-host.ts
|
|
5780
5786
|
/**
|
|
5781
|
-
* Which
|
|
5787
|
+
* Which machine an account owns, if any.
|
|
5782
5788
|
*
|
|
5783
|
-
* Durable facts only. Nothing here says whether the
|
|
5789
|
+
* Durable facts only. Nothing here says whether the machine can be reached or what
|
|
5784
5790
|
* is on it, because the database cannot see either; the relay answers both.
|
|
5785
5791
|
* Loading and error are absent for the same reason: they describe a request,
|
|
5786
5792
|
* not the account.
|
|
5787
5793
|
*/
|
|
5788
5794
|
/**
|
|
5789
|
-
* A paired
|
|
5795
|
+
* A paired machine, as the database knows it.
|
|
5790
5796
|
*
|
|
5791
|
-
* There is no availability here. Whether a
|
|
5797
|
+
* There is no availability here. Whether a machine is reachable is a live question
|
|
5792
5798
|
* the relay answers, and a read that guessed at it would be a second version of
|
|
5793
5799
|
* a fact it cannot see. `lastSeenAt` is null until a daemon has announced
|
|
5794
5800
|
* itself, because approving a grant is not an observation of one.
|
|
@@ -5796,7 +5802,11 @@ const HostPlatformSchema = _enum([
|
|
|
5796
5802
|
const PairedHostSchema = object({
|
|
5797
5803
|
name: string().min(1),
|
|
5798
5804
|
platform: HostPlatformSchema
|
|
5799
|
-
}).extend({
|
|
5805
|
+
}).extend({
|
|
5806
|
+
/** Names the pairing's relay object; a re-pair after unpair gets a new one. */
|
|
5807
|
+
id: HostIdSchema,
|
|
5808
|
+
lastSeenAt: IsoDateTimeSchema.nullable()
|
|
5809
|
+
});
|
|
5800
5810
|
discriminatedUnion("state", [
|
|
5801
5811
|
object({ state: literal("unpaired") }),
|
|
5802
5812
|
object({
|
|
@@ -5833,7 +5843,7 @@ const ProblemDetailsSchema = looseObject({
|
|
|
5833
5843
|
title: string(),
|
|
5834
5844
|
/**
|
|
5835
5845
|
* Safe human-readable explanation for this occurrence; clients must not parse it.
|
|
5836
|
-
* @example "Porte is not connected on the paired
|
|
5846
|
+
* @example "Porte is not connected on the paired machine."
|
|
5837
5847
|
*/
|
|
5838
5848
|
detail: string().optional(),
|
|
5839
5849
|
/**
|
|
@@ -6155,6 +6165,8 @@ const PORTE_CLI_CLIENT_ID = "porte-cli";
|
|
|
6155
6165
|
* serves it cannot drift; the route asserts it against the generated tree.
|
|
6156
6166
|
*/
|
|
6157
6167
|
const PAIRING_CODE_PATH = "/api/pair/code";
|
|
6168
|
+
/** Where the daemon ends its own pairing: DELETE with its bearer token. */
|
|
6169
|
+
const HOST_PAIRING_PATH = "/api/host/pairing";
|
|
6158
6170
|
/** RFC 8628 fixes this value; the token request is invalid without it. */
|
|
6159
6171
|
const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
6160
6172
|
/**
|
|
@@ -6223,7 +6235,7 @@ function formatPairingCode(code) {
|
|
|
6223
6235
|
/**
|
|
6224
6236
|
* Where the code was asked for, judged against where it is being answered.
|
|
6225
6237
|
*
|
|
6226
|
-
* The server compares, not the screen. Both halves normally happen on one
|
|
6238
|
+
* The server compares, not the screen. Both halves normally happen on one machine,
|
|
6227
6239
|
* so `elsewhere` is the whole signal: someone else's machine asked for a code
|
|
6228
6240
|
* you are about to approve.
|
|
6229
6241
|
*/
|
|
@@ -6705,4 +6717,4 @@ async function sendJsonRpcFrame(write) {
|
|
|
6705
6717
|
if (sent.isErr()) throw sent.error;
|
|
6706
6718
|
}
|
|
6707
6719
|
//#endregion
|
|
6708
|
-
export {
|
|
6720
|
+
export { turnIdFor as $, setLogSink as A, ElicitationNotFoundError as B, DeviceTokenErrorSchema as C, PAIRING_CODE_PATH as D, HOST_PAIRING_PATH as E, WorkspaceNotAllowedError as F, ConversationCursorSchema as G, TurnNotFoundError as H, AgentUnresponsiveError as I, ElicitationIdSchema as J, makeConversationSummary as K, ConfigurationNotFoundError as L, HostPlatformSchema as M, isClassifiedError as N, PORTE_CLI_CLIENT_ID as O, isDomainError as P, ToolCallIdSchema as Q, ConversationBusyError as R, DeviceCodeResponseSchema as S, DeviceTokenResponseSchema as T, ConversationEventSchema as U, PermissionNotFoundError as V, ToolViewSchema as W, MessageIdSchema as X, IsoDateTimeSchema as Y, PermissionIdSchema as Z, readJsonRpcIncoming as _, unknown as _t, HOST_APPLICATION_ERROR_CODE as a, h as at, DEVICE_CODE_GRANT_TYPE as b, HOST_CONVERSATION_SUBPROTOCOL as c, discriminatedUnion as ct, JSON_RPC_ERROR_CODES as d, json as dt, INTERNAL_SERVER_ERROR as et, JsonRpcReadError as f, literal as ft, jsonRpcNotification as g, string as gt, jsonRpcError as h, record as ht, HostControlMethods as i, CodingAgentUnavailableError as it, ProblemDetailsSchema as j, createLogger as k, HostRequestIdSchema as l, email as lt, handleJsonRpcRequest as m, object as mt, sendJsonRpcFrame as n, ServiceUnavailableError as nt, HOST_APPLICATION_ERROR_MESSAGE as o, _enum as ot, JsonRpcTextSchema as p, number as pt, ConversationIdSchema as q, HostConversationMethods as r, CODING_AGENT_UNAVAILABLE_ERROR as rt, HOST_CONTROL_SUBPROTOCOL as s, array as st, JsonRpcSendError as t, InternalServerError as tt, SequenceNumberSchema as u, httpUrl as ut, readJsonRpcTextFrame as v, url as vt, DeviceTokenRequestSchema as w, DeviceCodeRequestSchema as x, formatPairingCode as y, ConversationNotFoundError as z };
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { A as setLogSink } from "./client-BwrMf7hy.js";
|
|
3
3
|
//#region src/main.ts
|
|
4
4
|
const major = Number(process.versions.node.split(".")[0]);
|
|
5
5
|
if (Number.isNaN(major) || major < 22) {
|
|
@@ -11,7 +11,7 @@ setLogSink((_level, line) => {
|
|
|
11
11
|
});
|
|
12
12
|
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
13
13
|
process.env.LOG_LEVEL ??= verbose ? "DEBUG" : "WARN";
|
|
14
|
-
const { run } = await import("./run-cli-
|
|
14
|
+
const { run } = await import("./run-cli-CEPB1b8j.js");
|
|
15
15
|
const code = await run(process.argv.slice(2), {
|
|
16
16
|
stdout: process.stdout,
|
|
17
17
|
stderr: process.stderr,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as turnIdFor, B as ElicitationNotFoundError, C as DeviceTokenErrorSchema, D as PAIRING_CODE_PATH, E as HOST_PAIRING_PATH, F as WorkspaceNotAllowedError, G as ConversationCursorSchema, H as TurnNotFoundError, I as AgentUnresponsiveError, J as ElicitationIdSchema, K as makeConversationSummary, L as ConfigurationNotFoundError, M as HostPlatformSchema, N as isClassifiedError, O as PORTE_CLI_CLIENT_ID, P as isDomainError, Q as ToolCallIdSchema, R as ConversationBusyError, S as DeviceCodeResponseSchema, T as DeviceTokenResponseSchema, U as ConversationEventSchema, V as PermissionNotFoundError, W as ToolViewSchema, X as MessageIdSchema, Y as IsoDateTimeSchema, Z as PermissionIdSchema, _ as readJsonRpcIncoming, _t as unknown, a as HOST_APPLICATION_ERROR_CODE, at as h, b as DEVICE_CODE_GRANT_TYPE, c as HOST_CONVERSATION_SUBPROTOCOL, ct as discriminatedUnion, d as JSON_RPC_ERROR_CODES, dt as json, et as INTERNAL_SERVER_ERROR, f as JsonRpcReadError, ft as literal, g as jsonRpcNotification, gt as string, h as jsonRpcError, ht as record, i as HostControlMethods, it as CodingAgentUnavailableError, j as ProblemDetailsSchema, k as createLogger, l as HostRequestIdSchema, lt as email, m as handleJsonRpcRequest, mt as object, n as sendJsonRpcFrame, nt as ServiceUnavailableError, o as HOST_APPLICATION_ERROR_MESSAGE, ot as _enum, p as JsonRpcTextSchema, pt as number, q as ConversationIdSchema, r as HostConversationMethods, rt as CODING_AGENT_UNAVAILABLE_ERROR, s as HOST_CONTROL_SUBPROTOCOL, st as array, t as JsonRpcSendError, tt as InternalServerError, u as SequenceNumberSchema, ut as httpUrl, v as readJsonRpcTextFrame, vt as url, w as DeviceTokenRequestSchema, x as DeviceCodeRequestSchema, y as formatPairingCode, z as ConversationNotFoundError } from "./client-BwrMf7hy.js";
|
|
2
2
|
import { homedir, hostname } from "node:os";
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { execFileSync, spawn } from "node:child_process";
|
|
@@ -98,7 +98,7 @@ var UnsupportedPlatformError = class extends h("UnsupportedPlatformError") {
|
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
/**
|
|
101
|
-
* What this
|
|
101
|
+
* What this machine calls itself.
|
|
102
102
|
*
|
|
103
103
|
* `platform` stays the runtime's own token, so what is stored is what Node
|
|
104
104
|
* reported. Turning `darwin` into `macOS` is the reader's job, and spelling it
|
|
@@ -168,7 +168,7 @@ var WebSocketHandlerError = class extends h("WebSocketHandlerError") {
|
|
|
168
168
|
//#endregion
|
|
169
169
|
//#region src/entrypoints/cli/version.ts
|
|
170
170
|
/** Package version. Source is `package.json`. */
|
|
171
|
-
const VERSION = "0.1.
|
|
171
|
+
const VERSION = "0.1.3";
|
|
172
172
|
//#endregion
|
|
173
173
|
//#region src/entrypoints/cli/cli-error.ts
|
|
174
174
|
/** Bad arguments or missing required flags. */
|
|
@@ -199,7 +199,7 @@ function formatError(error) {
|
|
|
199
199
|
if (error instanceof ConfigError || error instanceof UnsupportedPlatformError) body = `Error (ECONFIG): Porte configuration is invalid.\n${error.message}`;
|
|
200
200
|
else if (error instanceof HostNotPairedError) body = error.message;
|
|
201
201
|
else if (error instanceof WebSocketHandlerError || error instanceof JsonRpcSendError) body = "Error (ERELAY): host connection stopped. Restart `porte up`.";
|
|
202
|
-
else if (error instanceof WebSocketHandshakeRefused) body = error.status === 401 || error.status === 403 ? `Error (EAUTH): ${error.message} Run \`porte pair\` to pair this
|
|
202
|
+
else if (error instanceof WebSocketHandshakeRefused) body = error.status === 401 || error.status === 403 ? `Error (EAUTH): ${error.message} Run \`porte pair\` to pair this machine again.` : `Error (ERELAY): ${error.message}`;
|
|
203
203
|
else if (error instanceof WebSocketProtocolClose) body = `Error (ERELAY): ${error.message}`;
|
|
204
204
|
else if (error instanceof PairingError) body = `Error (EPAIR): ${error.message} Run \`porte pair\` to try again.`;
|
|
205
205
|
else body = `Error (ECRED): ${error.message}`;
|
|
@@ -340,7 +340,7 @@ function reportUnexpectedCliError(stderr, cause) {
|
|
|
340
340
|
//#endregion
|
|
341
341
|
//#region src/application/commands/pair-host.ts
|
|
342
342
|
/**
|
|
343
|
-
* Pair this
|
|
343
|
+
* Pair this machine with a Porte account.
|
|
344
344
|
*
|
|
345
345
|
* The daemon asks for a code, shows it, and waits. Approval happens on the
|
|
346
346
|
* person's phone, so this machine never handles a password and the credential
|
|
@@ -538,13 +538,30 @@ var DeviceAuthorizationClient = class {
|
|
|
538
538
|
return fromRefusal(refusal.data.error);
|
|
539
539
|
}
|
|
540
540
|
/**
|
|
541
|
-
* End the pairing.
|
|
541
|
+
* End the pairing on the server, before the credential is dropped locally.
|
|
542
542
|
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
543
|
+
* A refused token means the browser already ended it, which is the state
|
|
544
|
+
* asked for. Anything else stays an error, so the caller keeps the credential
|
|
545
|
+
* and can try again: a pairing the server still holds is what nothing else can clean up.
|
|
545
546
|
*/
|
|
546
|
-
revoke(
|
|
547
|
-
|
|
547
|
+
async revoke(token) {
|
|
548
|
+
let response;
|
|
549
|
+
try {
|
|
550
|
+
response = await fetch(new URL(HOST_PAIRING_PATH, this.baseUrl), {
|
|
551
|
+
method: "DELETE",
|
|
552
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
553
|
+
});
|
|
554
|
+
} catch (cause) {
|
|
555
|
+
throw new PairingError({
|
|
556
|
+
reason: "unreachable",
|
|
557
|
+
cause
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (response.ok || response.status === 401 || response.status === 403) return;
|
|
561
|
+
throw new PairingError({
|
|
562
|
+
reason: "unexpected",
|
|
563
|
+
cause: response.status
|
|
564
|
+
});
|
|
548
565
|
}
|
|
549
566
|
/**
|
|
550
567
|
* Ask who the new token belongs to.
|
|
@@ -708,7 +725,7 @@ async function runPairCommand(input) {
|
|
|
708
725
|
onPrompt: (prompt) => {
|
|
709
726
|
const shown = formatPairingCode(prompt.userCode);
|
|
710
727
|
const waiting = `Waiting for approval — the code expires in ${String(Math.round(prompt.expiresInSeconds / 60))} minutes. ${WAITING_EMOJI}`;
|
|
711
|
-
output.title("Pair this
|
|
728
|
+
output.title("Pair this machine with Porte", PAIR_EMOJI);
|
|
712
729
|
if (!interactive) {
|
|
713
730
|
output.raw(`First copy your pairing code: ${code(shown)}`);
|
|
714
731
|
output.raw(`Then open ${url(prompt.verificationUri)} in your browser.`);
|
|
@@ -754,7 +771,7 @@ async function runPairCommand(input) {
|
|
|
754
771
|
const machine = strong(describeThisMachine().name);
|
|
755
772
|
output.done(account === null ? `Paired ${machine} with Porte` : `Paired ${machine} with ${strong(account)} on Porte`);
|
|
756
773
|
output.blank();
|
|
757
|
-
output.raw(` Run ${code("porte up")} to control this
|
|
774
|
+
output.raw(` Run ${code("porte up")} to control this machine's Grok conversations from anywhere`);
|
|
758
775
|
output.note("Expires in 7 days if it never connects");
|
|
759
776
|
return 0;
|
|
760
777
|
}
|
|
@@ -772,8 +789,8 @@ Options:
|
|
|
772
789
|
-v, --verbose Write debug logs to stderr
|
|
773
790
|
|
|
774
791
|
Commands:
|
|
775
|
-
pair Link this
|
|
776
|
-
unpair End this
|
|
792
|
+
pair Link this machine to your Porte account
|
|
793
|
+
unpair End this machine's pairing
|
|
777
794
|
up Connect this host to Porte
|
|
778
795
|
|
|
779
796
|
Examples:
|
|
@@ -784,7 +801,7 @@ Examples:
|
|
|
784
801
|
const PAIR_HELP = `Usage:
|
|
785
802
|
porte pair
|
|
786
803
|
|
|
787
|
-
Link this
|
|
804
|
+
Link this machine to your Porte account. Prints a code to approve in any browser,
|
|
788
805
|
then waits. Run this once; \`porte up\` uses what it stores.
|
|
789
806
|
|
|
790
807
|
Options:
|
|
@@ -798,7 +815,7 @@ Environment:
|
|
|
798
815
|
const UNPAIR_HELP = `Usage:
|
|
799
816
|
porte unpair
|
|
800
817
|
|
|
801
|
-
End this
|
|
818
|
+
End this machine's pairing. Porte stops accepting it, and the local credential is
|
|
802
819
|
deleted. Run \`porte pair\` to connect it again.
|
|
803
820
|
|
|
804
821
|
Options:
|
|
@@ -4149,12 +4166,12 @@ async function runUnpairCommand(input) {
|
|
|
4149
4166
|
const output = createOutput(input.stderr);
|
|
4150
4167
|
const stored = await resources.credentials.read();
|
|
4151
4168
|
if (stored === null) {
|
|
4152
|
-
output.done("This
|
|
4169
|
+
output.done("This machine is not paired.");
|
|
4153
4170
|
return 0;
|
|
4154
4171
|
}
|
|
4155
4172
|
await resources.authorizer.revoke(stored.token);
|
|
4156
4173
|
await resources.credentials.clear();
|
|
4157
|
-
output.done(`Unpaired this
|
|
4174
|
+
output.done(`Unpaired this machine from ${output.emphasis.strong(new URL(stored.baseUrl).host)}`);
|
|
4158
4175
|
return 0;
|
|
4159
4176
|
}
|
|
4160
4177
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://www.schemastore.org/package.json",
|
|
3
3
|
"name": "@porte/cli",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"description": "Run your local Grok sessions from your phone. The daemon that pairs a
|
|
4
|
+
"version": "0.1.3",
|
|
5
|
+
"description": "Run your local Grok sessions from your phone. The daemon that pairs a machine with useporte.dev.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"acp",
|
|
8
8
|
"agent",
|
|
@@ -51,6 +51,9 @@
|
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"dev": "tsx src/main.ts",
|
|
54
|
+
"dev:pair": "PORTE_URL=https://tunnel.useporte.dev PORTE_DATA_DIRECTORY=$HOME/.porte-dev tsx src/main.ts pair",
|
|
55
|
+
"dev:up": "PORTE_URL=https://tunnel.useporte.dev PORTE_DATA_DIRECTORY=$HOME/.porte-dev tsx src/main.ts up",
|
|
56
|
+
"dev:unpair": "PORTE_URL=https://tunnel.useporte.dev PORTE_DATA_DIRECTORY=$HOME/.porte-dev tsx src/main.ts unpair",
|
|
54
57
|
"build": "tsdown",
|
|
55
58
|
"typecheck": "tsc --noEmit",
|
|
56
59
|
"lint": "oxlint .",
|