grokbot-control 0.0.0-stage → 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.
Files changed (122) hide show
  1. package/README.md +160 -2
  2. package/dist/auth/lock.d.ts +14 -0
  3. package/dist/auth/lock.js +71 -0
  4. package/dist/auth/provider.d.ts +89 -0
  5. package/dist/auth/provider.js +884 -0
  6. package/dist/auth/store.d.ts +25 -0
  7. package/dist/auth/store.js +106 -0
  8. package/dist/auth/types.d.ts +138 -0
  9. package/dist/auth/types.js +1 -0
  10. package/dist/cli/config.d.ts +26 -0
  11. package/dist/cli/config.js +98 -0
  12. package/dist/cli/help.d.ts +1 -0
  13. package/dist/cli/help.js +83 -0
  14. package/dist/cli/io.d.ts +6 -0
  15. package/dist/cli/io.js +61 -0
  16. package/dist/cli/main.d.ts +2 -0
  17. package/dist/cli/main.js +275 -0
  18. package/dist/cli/sync.d.ts +4 -0
  19. package/dist/cli/sync.js +105 -0
  20. package/dist/connect/client.d.ts +32 -0
  21. package/dist/connect/client.js +257 -0
  22. package/dist/dto/result.d.ts +33 -0
  23. package/dist/dto/result.js +29 -0
  24. package/dist/dto/types.d.ts +63 -0
  25. package/dist/dto/types.js +2 -0
  26. package/dist/errors.d.ts +31 -0
  27. package/dist/errors.js +64 -0
  28. package/dist/index.d.ts +13 -0
  29. package/dist/index.js +11 -0
  30. package/dist/mcp/main.d.ts +2 -0
  31. package/dist/mcp/main.js +82 -0
  32. package/dist/mcp/tools.d.ts +24 -0
  33. package/dist/mcp/tools.js +408 -0
  34. package/dist/mcp/zod-json.d.ts +2 -0
  35. package/dist/mcp/zod-json.js +4 -0
  36. package/dist/ops/auth.d.ts +48 -0
  37. package/dist/ops/auth.js +89 -0
  38. package/dist/ops/bot.d.ts +52 -0
  39. package/dist/ops/bot.js +211 -0
  40. package/dist/ops/limits.d.ts +10 -0
  41. package/dist/ops/limits.js +31 -0
  42. package/dist/ops/map.d.ts +7 -0
  43. package/dist/ops/map.js +57 -0
  44. package/dist/ops/message.d.ts +33 -0
  45. package/dist/ops/message.js +118 -0
  46. package/dist/ops/plane.d.ts +26 -0
  47. package/dist/ops/plane.js +33 -0
  48. package/dist/ops/progress.d.ts +34 -0
  49. package/dist/ops/progress.js +92 -0
  50. package/dist/ops/session.d.ts +13 -0
  51. package/dist/ops/session.js +34 -0
  52. package/dist/ops/sidebar.d.ts +70 -0
  53. package/dist/ops/sidebar.js +205 -0
  54. package/dist/sync/attachments.d.ts +76 -0
  55. package/dist/sync/attachments.js +274 -0
  56. package/dist/sync/core.d.ts +92 -0
  57. package/dist/sync/core.js +1027 -0
  58. package/dist/sync/decode.d.ts +83 -0
  59. package/dist/sync/decode.js +229 -0
  60. package/dist/sync/download-policy.d.ts +3 -0
  61. package/dist/sync/download-policy.js +103 -0
  62. package/dist/sync/fs.d.ts +24 -0
  63. package/dist/sync/fs.js +252 -0
  64. package/dist/sync/hydrate.d.ts +43 -0
  65. package/dist/sync/hydrate.js +472 -0
  66. package/dist/sync/render.d.ts +5 -0
  67. package/dist/sync/render.js +100 -0
  68. package/dist/testing/mock-persist.d.ts +3 -0
  69. package/dist/testing/mock-persist.js +45 -0
  70. package/dist/testing/mock-transport.d.ts +18 -0
  71. package/dist/testing/mock-transport.js +202 -0
  72. package/docs/acceptance.md +19 -0
  73. package/docs/architecture.md +128 -0
  74. package/docs/auth-lock.md +87 -0
  75. package/docs/implementation-plan.md +14 -0
  76. package/docs/implementation-status.md +92 -0
  77. package/docs/proto-drift.md +9 -0
  78. package/docs/protocol/README.md +34 -0
  79. package/docs/protocol/attachment-pagination-0.79.3.md +32 -0
  80. package/docs/protocol/auth-flow.md +51 -0
  81. package/docs/protocol/baseline-manifest.md +72 -0
  82. package/docs/protocol/baselines/full-schema-diff-0.78.4-to-0.79.2.txt +239 -0
  83. package/docs/protocol/baselines/full-schema-diff-0.78.4-to-0.79.3.txt +240 -0
  84. package/docs/protocol/baselines/full-schema-diff-0.79.2-to-0.79.3.txt +23 -0
  85. package/docs/protocol/baselines/schema-0.79.3/README.md +7 -0
  86. package/docs/protocol/baselines/schema-0.79.3/agent.v1.proto +5184 -0
  87. package/docs/protocol/baselines/schema-0.79.3/aiserver.v1.proto +18182 -0
  88. package/docs/protocol/baselines/schema-0.79.3/aiserver.v1_2.proto +54 -0
  89. package/docs/protocol/baselines/schema-0.79.3/anyrun.v1.proto +37 -0
  90. package/docs/protocol/baselines/schema-0.79.3/internapi.v1.proto +35 -0
  91. package/docs/protocol/baselines/schema-0.79.3/snapshot.json.gz +0 -0
  92. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.AgentStoreService.proto +34 -0
  93. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.AiService.proto +17 -0
  94. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.AnalyticsService.proto +17 -0
  95. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.BackgroundComposerService.proto +19 -0
  96. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.DashboardService.proto +101 -0
  97. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.GrokBotService.proto +473 -0
  98. package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.SandBoxService.proto +64 -0
  99. package/docs/protocol/baselines/used-surface-0.78.4.json +3694 -0
  100. package/docs/protocol/baselines/used-surface-0.79.2.json +3835 -0
  101. package/docs/protocol/baselines/used-surface-0.79.3.json +3835 -0
  102. package/docs/protocol/baselines/used-surface-diff-0.78.4-to-0.79.2.md +34 -0
  103. package/docs/protocol/baselines/used-surface-diff-0.78.4-to-0.79.3.md +33 -0
  104. package/docs/protocol/compat-matrix.md +20 -0
  105. package/docs/protocol/static-restore.md +64 -0
  106. package/docs/protocol/surface-map.md +39 -0
  107. package/docs/protocol/transcript-body-0.79.3.md +98 -0
  108. package/docs/protocol/update-runbook.md +30 -0
  109. package/docs/protocol-map.md +106 -0
  110. package/docs/references.md +55 -0
  111. package/docs/requirements.md +26 -0
  112. package/docs/review.md +36 -0
  113. package/docs/sync-format.md +98 -0
  114. package/package.json +60 -4
  115. package/vendor/proto-gen/NOTICE.md +23 -0
  116. package/vendor/proto-gen/agent.v1_pb.js +9503 -0
  117. package/vendor/proto-gen/aiserver.v1_2_pb.js +105 -0
  118. package/vendor/proto-gen/aiserver.v1_pb.js +33163 -0
  119. package/vendor/proto-gen/anyrun.v1_pb.js +64 -0
  120. package/vendor/proto-gen/internapi.v1_pb.js +60 -0
  121. package/vendor/proto-gen/package.json +10 -0
  122. package/vendor/proto-gen/svc_aiserver.v1.GrokBotService_connect.js +4193 -0
@@ -0,0 +1,25 @@
1
+ import { type LockWaitOptions } from "./lock.js";
2
+ import type { AuthKind, PendingLogin, StoredCredential } from "./types.js";
3
+ export declare function credentialsDir(): string;
4
+ export declare function redactCredential(c: StoredCredential): Omit<StoredCredential, "access_token" | "refresh_token"> & {
5
+ access_token: string;
6
+ refresh_token?: string;
7
+ };
8
+ export declare class FileCredentialStore {
9
+ credFile(kind: AuthKind, backend: string): string;
10
+ pendingFile(kind: AuthKind, backend: string): string;
11
+ load(kind: AuthKind, backend: string): StoredCredential | undefined;
12
+ save(cred: StoredCredential): void;
13
+ /**
14
+ * CAS save: only write when on-disk revision matches expectedRevision (undefined/0 = absent or legacy).
15
+ * Returns false without writing when revision diverged (logout / new login / peer refresh).
16
+ */
17
+ saveIfRevision(cred: StoredCredential, expectedRevision: number | undefined): boolean;
18
+ clear(kind: AuthKind, backend: string): boolean;
19
+ loadPending(kind: AuthKind, backend: string): PendingLogin | undefined;
20
+ savePending(p: PendingLogin): void;
21
+ clearPending(kind: AuthKind, backend: string): boolean;
22
+ }
23
+ /** Separate permanent native locks for refresh exchange and short credential mutations. */
24
+ export declare function withCredentialLock<T>(lockKey: string, fn: () => Promise<T>, options?: number | LockWaitOptions): Promise<T>;
25
+ export declare const withRefreshLock: typeof withCredentialLock;
@@ -0,0 +1,106 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { withAdvisoryLock } from "./lock.js";
5
+ export function credentialsDir() {
6
+ return process.env.GBT_CREDENTIALS_DIR?.trim() || path.join(os.homedir(), ".gbc");
7
+ }
8
+ function key(kind, backend) {
9
+ return Buffer.from(`${kind}|${backend}`).toString("base64url");
10
+ }
11
+ function ensureDir() {
12
+ const dir = credentialsDir();
13
+ fs.mkdirSync(dir, { mode: 0o700, recursive: true });
14
+ try {
15
+ fs.chmodSync(dir, 0o700);
16
+ }
17
+ catch {
18
+ /* best effort */
19
+ }
20
+ return dir;
21
+ }
22
+ /** Atomic 0600 JSON write (tmp + rename). */
23
+ function writeSecretJson(file, data) {
24
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
25
+ fs.writeFileSync(tmp, JSON.stringify(data), { mode: 0o600 });
26
+ fs.chmodSync(tmp, 0o600);
27
+ fs.renameSync(tmp, file);
28
+ }
29
+ export function redactCredential(c) {
30
+ return {
31
+ ...c,
32
+ access_token: c.access_token ? "[redacted]" : "",
33
+ refresh_token: c.refresh_token ? "[redacted]" : undefined,
34
+ };
35
+ }
36
+ export class FileCredentialStore {
37
+ credFile(kind, backend) {
38
+ return path.join(credentialsDir(), `cred-${key(kind, backend)}.json`);
39
+ }
40
+ pendingFile(kind, backend) {
41
+ return path.join(credentialsDir(), `pending-${key(kind, backend)}.json`);
42
+ }
43
+ load(kind, backend) {
44
+ const f = this.credFile(kind, backend);
45
+ if (!fs.existsSync(f))
46
+ return undefined;
47
+ const raw = JSON.parse(fs.readFileSync(f, "utf8"));
48
+ if (raw.kind !== kind || raw.backend !== backend) {
49
+ throw new Error("credential kind/backend mismatch — refusing to use");
50
+ }
51
+ return raw;
52
+ }
53
+ save(cred) {
54
+ ensureDir();
55
+ writeSecretJson(this.credFile(cred.kind, cred.backend), cred);
56
+ }
57
+ /**
58
+ * CAS save: only write when on-disk revision matches expectedRevision (undefined/0 = absent or legacy).
59
+ * Returns false without writing when revision diverged (logout / new login / peer refresh).
60
+ */
61
+ saveIfRevision(cred, expectedRevision) {
62
+ const cur = this.load(cred.kind, cred.backend);
63
+ const curRev = cur?.revision ?? 0;
64
+ const exp = expectedRevision ?? 0;
65
+ if (cur && curRev !== exp)
66
+ return false;
67
+ if (!cur && exp !== 0)
68
+ return false;
69
+ this.save(cred);
70
+ return true;
71
+ }
72
+ clear(kind, backend) {
73
+ const f = this.credFile(kind, backend);
74
+ if (!fs.existsSync(f))
75
+ return false;
76
+ fs.unlinkSync(f);
77
+ return true;
78
+ }
79
+ loadPending(kind, backend) {
80
+ const f = this.pendingFile(kind, backend);
81
+ if (!fs.existsSync(f))
82
+ return undefined;
83
+ const raw = JSON.parse(fs.readFileSync(f, "utf8"));
84
+ if (raw.v !== 1 || raw.kind !== kind || raw.backend !== backend) {
85
+ throw new Error("pending login kind/backend mismatch — refusing to use");
86
+ }
87
+ return raw;
88
+ }
89
+ savePending(p) {
90
+ ensureDir();
91
+ writeSecretJson(this.pendingFile(p.kind, p.backend), p);
92
+ }
93
+ clearPending(kind, backend) {
94
+ const f = this.pendingFile(kind, backend);
95
+ if (!fs.existsSync(f))
96
+ return false;
97
+ fs.unlinkSync(f);
98
+ return true;
99
+ }
100
+ }
101
+ /** Separate permanent native locks for refresh exchange and short credential mutations. */
102
+ export async function withCredentialLock(lockKey, fn, options = 30_000) {
103
+ const lock = path.join(ensureDir(), `refresh-${Buffer.from(lockKey).toString("base64url")}.lock`);
104
+ return withAdvisoryLock(lock, fn, typeof options === "number" ? { waitMs: options } : options);
105
+ }
106
+ export const withRefreshLock = withCredentialLock;
@@ -0,0 +1,138 @@
1
+ export type AuthKind = "cursor" | "xai";
2
+ /** How the local credential was obtained. */
3
+ export type LoginKind = "browser_deep_control" | "static" | "unknown";
4
+ /** Last writer of the stored credential pair. */
5
+ export type CredentialSource = "login_poll" | "refresh" | "migrated" | "static";
6
+ /**
7
+ * Disk credential. Tokens never leave AuthOps/CLI/MCP envelopes.
8
+ * Provenance fields bind refresh to a backend/client; old files without them
9
+ * require GBT_OAUTH_CLIENT_ID and/or GBT_AUTH_CONFIRM_MIGRATE=1.
10
+ */
11
+ export type StoredCredential = {
12
+ kind: AuthKind;
13
+ /** Backend base URL this credential is bound to. */
14
+ backend: string;
15
+ access_token: string;
16
+ refresh_token?: string;
17
+ /** From JWT `exp` when present and finite. */
18
+ expires_at_ms?: number;
19
+ account_label?: string;
20
+ updated_at_ms: number;
21
+ /** Monotonic CAS counter; bumps on every successful save under the credential lock. */
22
+ revision?: number;
23
+ refresh_cooldown_until_ms?: number;
24
+ login_kind?: LoginKind;
25
+ credential_source?: CredentialSource;
26
+ /** Origin+path of the refresh POST (e.g. https://api2.cursor.sh/oauth/token). */
27
+ refresh_endpoint?: string;
28
+ /** Public OAuth client_id bound at login/refresh; never guessed from JWT. */
29
+ client_id?: string;
30
+ /** Optional JWT iss (display/binding only; unverified). */
31
+ issuer?: string;
32
+ /** Optional JWT sub (account subject binding). */
33
+ account_subject?: string;
34
+ /** Local session binding id; changes on each completed login. */
35
+ session_id?: string;
36
+ };
37
+ /** Pending browser login (0600 file, expires). Contains the secret verifier — never print. */
38
+ export type PendingLogin = {
39
+ v: 1;
40
+ kind: AuthKind;
41
+ backend: string;
42
+ website: string;
43
+ uuid: string;
44
+ verifier: string;
45
+ redirect_target: string;
46
+ created_at_ms: number;
47
+ expires_at_ms: number;
48
+ };
49
+ /** Safe to print: no verifier / tokens. */
50
+ export type AuthBeginResult = {
51
+ authorization_url: string;
52
+ uuid: string;
53
+ expires_at_ms: number;
54
+ note: string;
55
+ };
56
+ export type AuthCompleteResult = {
57
+ status: "completed";
58
+ kind: AuthKind;
59
+ backend: string;
60
+ account_label?: string;
61
+ expires_at_ms?: number;
62
+ revision?: number;
63
+ session_id?: string;
64
+ } | {
65
+ status: "pending";
66
+ expires_at_ms: number;
67
+ note: string;
68
+ };
69
+ export type RefreshReason = "forced" | "near_expiry" | "missing_exp" | "illegal_exp" | "already_fresh" | "peer_refreshed";
70
+ /** Non-secret refresh outcome for CLI/MCP/AuthOps. */
71
+ export type RefreshResult = {
72
+ refreshed: boolean;
73
+ reason: RefreshReason;
74
+ kind: AuthKind;
75
+ backend: string;
76
+ expires_at_ms?: number;
77
+ revision: number;
78
+ account_label?: string;
79
+ session_id?: string;
80
+ credential_source?: CredentialSource;
81
+ login_kind?: LoginKind;
82
+ client_id_bound: boolean;
83
+ refresh_endpoint?: string;
84
+ note?: string;
85
+ };
86
+ export type WhoamiLocal = {
87
+ kind: AuthKind;
88
+ backend: string;
89
+ account_label?: string;
90
+ has_token: boolean;
91
+ expires_at_ms?: number;
92
+ refresh_configured: boolean;
93
+ pending_login: boolean;
94
+ revision?: number;
95
+ session_id?: string;
96
+ credential_source?: CredentialSource;
97
+ login_kind?: LoginKind;
98
+ client_id_bound: boolean;
99
+ refresh_endpoint?: string;
100
+ disk_refresh: boolean;
101
+ static_token: boolean;
102
+ };
103
+ export type AuthProvider = {
104
+ kind: AuthKind;
105
+ backend: string;
106
+ getAccessToken(opts?: {
107
+ force?: boolean;
108
+ }): Promise<string | undefined>;
109
+ /** Start independent browser login; persists pending state (0600). */
110
+ beginLogin(): Promise<AuthBeginResult>;
111
+ /** Single poll attempt against the persisted pending login. */
112
+ pollLogin(): Promise<AuthCompleteResult>;
113
+ /** Poll until completed, timeout, cancel or 3 consecutive non-404 failures. */
114
+ completeLogin(opts?: {
115
+ timeoutMs?: number;
116
+ signal?: AbortSignal;
117
+ }): Promise<AuthCompleteResult>;
118
+ cancelLogin(): Promise<{
119
+ had_pending: boolean;
120
+ }>;
121
+ /**
122
+ * Refresh when near expiry / missing exp, or when force=true. Never prints tokens.
123
+ * `rejectedAccessToken`: on 401 recovery, pass the bearer the server rejected; if disk
124
+ * already has a different access token, adopt it without another force rotation.
125
+ */
126
+ refresh(opts?: {
127
+ force?: boolean;
128
+ signal?: AbortSignal;
129
+ rejectedAccessToken?: string;
130
+ }): Promise<RefreshResult>;
131
+ /** Local only: removes this tool's own credential + pending files. Does not revoke server-side. */
132
+ logout(): Promise<{
133
+ removed_credential: boolean;
134
+ removed_pending: boolean;
135
+ }>;
136
+ /** Pure: no network. */
137
+ whoamiLocal(): Promise<WhoamiLocal>;
138
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import type { AuthKind } from "../auth/types.js";
2
+ import { ControlPlane } from "../ops/plane.js";
3
+ import type { MockState } from "../testing/mock-transport.js";
4
+ export type CliGlobal = {
5
+ json: boolean;
6
+ allowWrites: boolean;
7
+ dryRun: boolean;
8
+ baseUrl: string;
9
+ authKind: AuthKind;
10
+ mock: boolean;
11
+ trustBackend: boolean;
12
+ };
13
+ export declare function parseGlobals(argv: string[]): {
14
+ globals: CliGlobal;
15
+ rest: string[];
16
+ };
17
+ /**
18
+ * Value flag lookup. `--x=value` always takes value verbatim (use this form for
19
+ * values beginning with "-"). `--x value` form: a missing value or a next token
20
+ * starting with "--" is a usage error (exit 2) — never swallow the next flag.
21
+ */
22
+ export declare function flag(rest: string[], name: string): string | undefined;
23
+ export declare function hasFlag(rest: string[], name: string): boolean;
24
+ export declare function buildPlane(globals: CliGlobal): ControlPlane;
25
+ export declare function persistMockState(): void;
26
+ export declare function getMockState(): MockState | undefined;
@@ -0,0 +1,98 @@
1
+ import { GbcError } from "../errors.js";
2
+ import { createAuthProvider, staticTokenProvider } from "../auth/provider.js";
3
+ import { ControlPlane } from "../ops/plane.js";
4
+ import { createMockTransport } from "../testing/mock-transport.js";
5
+ import { loadMockState, saveMockState } from "../testing/mock-persist.js";
6
+ export function parseGlobals(argv) {
7
+ const globals = {
8
+ json: false,
9
+ allowWrites: false,
10
+ dryRun: false,
11
+ baseUrl: process.env.GBT_BASE_URL || "https://api2.cursor.sh",
12
+ authKind: process.env.GBT_AUTH_KIND || "cursor",
13
+ mock: process.env.GBT_MOCK === "1",
14
+ trustBackend: process.env.GBT_TRUST_BACKEND === "1",
15
+ };
16
+ const rest = [];
17
+ for (let i = 0; i < argv.length; i++) {
18
+ const a = argv[i];
19
+ if (a === "--json")
20
+ globals.json = true;
21
+ else if (a === "--allow-writes")
22
+ globals.allowWrites = true;
23
+ else if (a === "--dry-run")
24
+ globals.dryRun = true;
25
+ else if (a === "--mock")
26
+ globals.mock = true;
27
+ else if (a === "--trust-backend")
28
+ globals.trustBackend = true;
29
+ else if (a === "--base-url")
30
+ globals.baseUrl = argv[++i] || "";
31
+ else if (a.startsWith("--base-url="))
32
+ globals.baseUrl = a.slice("--base-url=".length);
33
+ else if (a === "--auth-kind")
34
+ globals.authKind = argv[++i] || "cursor";
35
+ else if (a.startsWith("--auth-kind="))
36
+ globals.authKind = a.slice("--auth-kind=".length);
37
+ else
38
+ rest.push(a);
39
+ }
40
+ return { globals, rest };
41
+ }
42
+ /**
43
+ * Value flag lookup. `--x=value` always takes value verbatim (use this form for
44
+ * values beginning with "-"). `--x value` form: a missing value or a next token
45
+ * starting with "--" is a usage error (exit 2) — never swallow the next flag.
46
+ */
47
+ export function flag(rest, name) {
48
+ const pref = rest.find((x) => x.startsWith(`${name}=`));
49
+ if (pref !== undefined)
50
+ return pref.slice(name.length + 1);
51
+ const idx = rest.indexOf(name);
52
+ if (idx < 0)
53
+ return undefined;
54
+ const v = rest[idx + 1];
55
+ if (v === undefined || v.startsWith("--")) {
56
+ throw new GbcError("usage", `${name} requires a value`, {
57
+ exitCode: 2,
58
+ hint: `pass ${name} <value>, or ${name}=<value> for values starting with "-"`,
59
+ });
60
+ }
61
+ return v;
62
+ }
63
+ export function hasFlag(rest, name) {
64
+ return rest.includes(name) || rest.some((x) => x.startsWith(`${name}=`));
65
+ }
66
+ let sharedMockState;
67
+ export function buildPlane(globals) {
68
+ if (globals.mock) {
69
+ sharedMockState ??= loadMockState();
70
+ return new ControlPlane({
71
+ baseUrl: "mock://gbc",
72
+ transport: createMockTransport(sharedMockState),
73
+ simulated: true,
74
+ auth: staticTokenProvider({
75
+ kind: globals.authKind,
76
+ backend: globals.baseUrl,
77
+ accessToken: "mock-token",
78
+ }),
79
+ });
80
+ }
81
+ const auth = createAuthProvider({
82
+ kind: globals.authKind,
83
+ backend: globals.baseUrl,
84
+ trustCustomBackend: globals.trustBackend,
85
+ });
86
+ return new ControlPlane({
87
+ baseUrl: globals.baseUrl,
88
+ auth,
89
+ simulated: false,
90
+ });
91
+ }
92
+ export function persistMockState() {
93
+ if (sharedMockState)
94
+ saveMockState(sharedMockState);
95
+ }
96
+ export function getMockState() {
97
+ return sharedMockState;
98
+ }
@@ -0,0 +1 @@
1
+ export declare const HELP = "gbc \u2014 Grok Bot control CLI (programmable App-equivalent control plane)\n\nUSAGE:\n gbc [--json] [--mock] [--allow-writes] [--dry-run] [--base-url URL] [--auth-kind cursor|xai] <noun> <verb> [flags]\n\nEXAMPLES:\n gbc --mock --json bot create --name demo --description \"test\"\n gbc --mock --json bot list\n gbc --mock --json bot get --agent-id <id>\n gbc --mock --json --allow-writes --dry-run bot delete --agent-id <id> --confirm=<id>\n gbc --mock --json --allow-writes message send --agent-id <id> --session-id <sid> --text \"hi\"\n gbc --mock --json message status --agent-id <id> --session-id <sid> --message-id <mid>\n gbc --mock --json message history --agent-id <id>\n gbc --mock --json session list --agent-id <id>\n gbc --mock --json bot status --agent-id <id>\n gbc --mock --json --allow-writes sidebar create --name Work\n gbc --mock --json --allow-writes sidebar move --agent-id <id> --section-id <sid>\n gbc --mock --json auth whoami\n gbc --json auth login-begin # prints authorization_url; open it in a browser\n gbc --json auth complete --timeout 300 # polls until signed in (exit 8 auth_pending on timeout)\n gbc --json auth login --timeout 300 # begin+complete; URL as stderr {\"event\":\"open_browser\"}\n gbc --json auth whoami | auth poll | auth cancel | auth refresh [--force] | auth logout\n\nSYNC (file-first snapshots; no SQLite):\n gbc sync pull --agent-id <id> [--session-id <sid>] --dir /absolute/private/root \\\n --account-id <stable-id> --team-id <stable-team-id|none> --ack-unverified-scope\n gbc sync status --dir /absolute/private/root [--agent-id <id>] [--session-id <sid>]\n gbc sync render --dir /absolute/private/root [--agent-id <id>] [--session-id <sid>]\n gbc sync attachments --dir /absolute/private/root [--agent-id <id>] [--session-id <sid>] [--path <remote>]\n pull bounds: --page-size 100 --max-pages 100 --max-bytes 33554432 --timeout-ms 30000 (defaults)\n body hydration: --hydrate off|missing (default off); inline first, exact omitted re-list, store blob reads.\n hydration bounds: --max-hydrate-requests 32 --hydrate-max-bytes 8388608 --hydrate-max-body-bytes 4194304\n Hard caps: 256 extra RPCs / 64MiB aggregate / 4MiB body; share pull byte/time budget. Partial -> exit 9.\n attachments bounds: --max-file-bytes 33554432 --max-bytes 67108864 --timeout-ms 30000\n --dry-run pull/render/attachments: no RPC or files. --all is not supported in v1.\n attachments also requires --account-id/--team-id/--ack-unverified-scope.\n CLI pull/render/attachments authorize local writes; --allow-writes is NOT required.\n Identity is operator-asserted, NOT verified by auth. Use stable IDs, never email/token hash.\n Existing roots must be private 0700; files 0600. status/render are local and need no login.\n Raw bytes/base64 preserved. render exposes known JSON message/text/notice/tool-summary shapes.\n Raw observed envelopes stay unchanged; recovered bodies carry version-bound provenance/local content hash.\n Unknown cards/unrecovered bodies remain placeholders; no decryption. JSONL/MD grep works offline.\n Line coverage and body coverage are separate; completeness/current membership always unknown. No Watch/deletion.\n attachments: on-demand ResolveGrokBotAttachmentReads for synced user-attachment and send-message attachment App store aliases only.\n MCP: configure GBT_SYNC_ROOT; sync_status is local. sync_pull/render/attachments require independent\n GBT_SYNC_ALLOW_LOCAL_WRITES=1 plus GBT_SYNC_ACCOUNT_ID, GBT_SYNC_TEAM_ID,\n GBT_SYNC_ACK_UNVERIFIED_SCOPE=1 for pull. READONLY excludes sync mutators. No archive path tool argument.\n Head refresh/backfill alternate; retained observations, NOT a current mirror.\n\nNOTES:\n - Default transport is live Connect to --base-url (requires auth). Use --mock only for explicit simulation.\n - Live auth/RPC against production is NOT verified in this build's automated tests.\n - Remote writes require --allow-writes (or GBT_ALLOW_WRITES=1). --dry-run is pure (no RPC).\n - bot delete / sidebar delete / interrupt require --confirm=<resource-id> (exact match).\n - Value flags: \"--flag value\" or \"--flag=value\". A missing value (or a next token starting\n with \"--\") is a usage error (exit 2); use --text=--literal for values starting with \"-\".\n - --limit must be an integer 1..1000 (default 50); invalid values exit 2.\n - Write errors that cannot rule out a side effect (unavailable/internal/deadline/transport)\n exit 10 (unknown, retryable=false): reconcile before retrying.\n - Sidebar writes are read back; a semantic mismatch exits 10 with code sidebar_unverified.\n - GBT_TRUST_BACKEND / --trust-backend: only the exact value \"1\" trusts a custom --base-url.\n - Auth: independent Cursor browser login (static-RE protocol, not official API). Verifier only in a\n 0600 pending file (15 min); credentials in $GBT_CREDENTIALS_DIR (default ~/.gbc, 0600). Tokens and\n verifier are never printed. xai kind: unsupported. Refresh needs GBT_OAUTH_CLIENT_ID or a\n login-persisted client_id binding (never hardcoded / never guessed from JWT). auth refresh\n without --force may report already_fresh (no network); --force always attempts refresh.\n whoami/status stay offline. RPC 401 updates creds for next request; write RPCs are never auto-\n replayed; whitelisted read-only unary may refresh+retry once. logout/login share the credential\n lock with refresh. Not logged in / pre-RPC refresh failure -> structured auth/retryable error with rpc_dispatched=false.\n - Native auth lock: fs-native-extensions@1.5.3, Node20+, Linux/macOS/Windows prebuilds.\n Missing binding fails closed: npm ci; see docs/auth-lock.md. Local filesystem only; Windows\n ACL privacy requires operator verification. Stop ALL old MCPs before upgrading (lock namespaces differ).\n Wait deadline 30s, refresh HTTP <=20s; wait cancellation never abandons an acquired exchange.\n - MCP cancel \u2260 Interrupt. Disconnect does not interrupt runs.\n - Room commands are not mixed into sidebar.\n\nEXIT CODES:\n 0 success | 2 usage | 4 auth | 5 not_found | 6 conflict | 7 denied\n 8 retryable | 9 known_failure | 10 unknown outcome | 1 other\n\nMCP:\n gbc-mcp # stdio MCP server (same SDK ops)\n";
@@ -0,0 +1,83 @@
1
+ export const HELP = `gbc — Grok Bot control CLI (programmable App-equivalent control plane)
2
+
3
+ USAGE:
4
+ gbc [--json] [--mock] [--allow-writes] [--dry-run] [--base-url URL] [--auth-kind cursor|xai] <noun> <verb> [flags]
5
+
6
+ EXAMPLES:
7
+ gbc --mock --json bot create --name demo --description "test"
8
+ gbc --mock --json bot list
9
+ gbc --mock --json bot get --agent-id <id>
10
+ gbc --mock --json --allow-writes --dry-run bot delete --agent-id <id> --confirm=<id>
11
+ gbc --mock --json --allow-writes message send --agent-id <id> --session-id <sid> --text "hi"
12
+ gbc --mock --json message status --agent-id <id> --session-id <sid> --message-id <mid>
13
+ gbc --mock --json message history --agent-id <id>
14
+ gbc --mock --json session list --agent-id <id>
15
+ gbc --mock --json bot status --agent-id <id>
16
+ gbc --mock --json --allow-writes sidebar create --name Work
17
+ gbc --mock --json --allow-writes sidebar move --agent-id <id> --section-id <sid>
18
+ gbc --mock --json auth whoami
19
+ gbc --json auth login-begin # prints authorization_url; open it in a browser
20
+ gbc --json auth complete --timeout 300 # polls until signed in (exit 8 auth_pending on timeout)
21
+ gbc --json auth login --timeout 300 # begin+complete; URL as stderr {"event":"open_browser"}
22
+ gbc --json auth whoami | auth poll | auth cancel | auth refresh [--force] | auth logout
23
+
24
+ SYNC (file-first snapshots; no SQLite):
25
+ gbc sync pull --agent-id <id> [--session-id <sid>] --dir /absolute/private/root \\
26
+ --account-id <stable-id> --team-id <stable-team-id|none> --ack-unverified-scope
27
+ gbc sync status --dir /absolute/private/root [--agent-id <id>] [--session-id <sid>]
28
+ gbc sync render --dir /absolute/private/root [--agent-id <id>] [--session-id <sid>]
29
+ gbc sync attachments --dir /absolute/private/root [--agent-id <id>] [--session-id <sid>] [--path <remote>]
30
+ pull bounds: --page-size 100 --max-pages 100 --max-bytes 33554432 --timeout-ms 30000 (defaults)
31
+ body hydration: --hydrate off|missing (default off); inline first, exact omitted re-list, store blob reads.
32
+ hydration bounds: --max-hydrate-requests 32 --hydrate-max-bytes 8388608 --hydrate-max-body-bytes 4194304
33
+ Hard caps: 256 extra RPCs / 64MiB aggregate / 4MiB body; share pull byte/time budget. Partial -> exit 9.
34
+ attachments bounds: --max-file-bytes 33554432 --max-bytes 67108864 --timeout-ms 30000
35
+ --dry-run pull/render/attachments: no RPC or files. --all is not supported in v1.
36
+ attachments also requires --account-id/--team-id/--ack-unverified-scope.
37
+ CLI pull/render/attachments authorize local writes; --allow-writes is NOT required.
38
+ Identity is operator-asserted, NOT verified by auth. Use stable IDs, never email/token hash.
39
+ Existing roots must be private 0700; files 0600. status/render are local and need no login.
40
+ Raw bytes/base64 preserved. render exposes known JSON message/text/notice/tool-summary shapes.
41
+ Raw observed envelopes stay unchanged; recovered bodies carry version-bound provenance/local content hash.
42
+ Unknown cards/unrecovered bodies remain placeholders; no decryption. JSONL/MD grep works offline.
43
+ Line coverage and body coverage are separate; completeness/current membership always unknown. No Watch/deletion.
44
+ attachments: on-demand ResolveGrokBotAttachmentReads for synced user-attachment and send-message attachment App store aliases only.
45
+ MCP: configure GBT_SYNC_ROOT; sync_status is local. sync_pull/render/attachments require independent
46
+ GBT_SYNC_ALLOW_LOCAL_WRITES=1 plus GBT_SYNC_ACCOUNT_ID, GBT_SYNC_TEAM_ID,
47
+ GBT_SYNC_ACK_UNVERIFIED_SCOPE=1 for pull. READONLY excludes sync mutators. No archive path tool argument.
48
+ Head refresh/backfill alternate; retained observations, NOT a current mirror.
49
+
50
+ NOTES:
51
+ - Default transport is live Connect to --base-url (requires auth). Use --mock only for explicit simulation.
52
+ - Live auth/RPC against production is NOT verified in this build's automated tests.
53
+ - Remote writes require --allow-writes (or GBT_ALLOW_WRITES=1). --dry-run is pure (no RPC).
54
+ - bot delete / sidebar delete / interrupt require --confirm=<resource-id> (exact match).
55
+ - Value flags: "--flag value" or "--flag=value". A missing value (or a next token starting
56
+ with "--") is a usage error (exit 2); use --text=--literal for values starting with "-".
57
+ - --limit must be an integer 1..1000 (default 50); invalid values exit 2.
58
+ - Write errors that cannot rule out a side effect (unavailable/internal/deadline/transport)
59
+ exit 10 (unknown, retryable=false): reconcile before retrying.
60
+ - Sidebar writes are read back; a semantic mismatch exits 10 with code sidebar_unverified.
61
+ - GBT_TRUST_BACKEND / --trust-backend: only the exact value "1" trusts a custom --base-url.
62
+ - Auth: independent Cursor browser login (static-RE protocol, not official API). Verifier only in a
63
+ 0600 pending file (15 min); credentials in $GBT_CREDENTIALS_DIR (default ~/.gbc, 0600). Tokens and
64
+ verifier are never printed. xai kind: unsupported. Refresh needs GBT_OAUTH_CLIENT_ID or a
65
+ login-persisted client_id binding (never hardcoded / never guessed from JWT). auth refresh
66
+ without --force may report already_fresh (no network); --force always attempts refresh.
67
+ whoami/status stay offline. RPC 401 updates creds for next request; write RPCs are never auto-
68
+ replayed; whitelisted read-only unary may refresh+retry once. logout/login share the credential
69
+ lock with refresh. Not logged in / pre-RPC refresh failure -> structured auth/retryable error with rpc_dispatched=false.
70
+ - Native auth lock: fs-native-extensions@1.5.3, Node20+, Linux/macOS/Windows prebuilds.
71
+ Missing binding fails closed: npm ci; see docs/auth-lock.md. Local filesystem only; Windows
72
+ ACL privacy requires operator verification. Stop ALL old MCPs before upgrading (lock namespaces differ).
73
+ Wait deadline 30s, refresh HTTP <=20s; wait cancellation never abandons an acquired exchange.
74
+ - MCP cancel ≠ Interrupt. Disconnect does not interrupt runs.
75
+ - Room commands are not mixed into sidebar.
76
+
77
+ EXIT CODES:
78
+ 0 success | 2 usage | 4 auth | 5 not_found | 6 conflict | 7 denied
79
+ 8 retryable | 9 known_failure | 10 unknown outcome | 1 other
80
+
81
+ MCP:
82
+ gbc-mcp # stdio MCP server (same SDK ops)
83
+ `;
@@ -0,0 +1,6 @@
1
+ import type { ResultEnvelope } from "../dto/result.js";
2
+ export declare function writeOut(data: unknown, asJson: boolean): void;
3
+ export declare function writeErrJson(data: unknown): void;
4
+ export declare function emitResult(r: ResultEnvelope, asJson: boolean): number;
5
+ export declare function usageExit(message: string, hint?: string): never;
6
+ export declare function catchToExit(e: unknown, asJson: boolean): number;
package/dist/cli/io.js ADDED
@@ -0,0 +1,61 @@
1
+ import { EXIT, GbcError, mapExitCode } from "../errors.js";
2
+ export function writeOut(data, asJson) {
3
+ if (asJson) {
4
+ process.stdout.write(JSON.stringify(data) + "\n");
5
+ }
6
+ else if (typeof data === "string") {
7
+ process.stdout.write(data + (data.endsWith("\n") ? "" : "\n"));
8
+ }
9
+ else {
10
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
11
+ }
12
+ }
13
+ export function writeErrJson(data) {
14
+ process.stderr.write(JSON.stringify(data) + "\n");
15
+ }
16
+ export function emitResult(r, asJson) {
17
+ if (r.ok) {
18
+ writeOut(asJson ? r : r.data ?? r, asJson || true);
19
+ // Always machine-friendly for agents: default JSON on stdout for structured data.
20
+ return EXIT.SUCCESS;
21
+ }
22
+ writeErrJson(r);
23
+ return mapExitCode(r.code, r.outcome, r.retryable);
24
+ }
25
+ export function usageExit(message, hint) {
26
+ writeErrJson({
27
+ schema_version: "1.0.0",
28
+ ok: false,
29
+ outcome: "known_failure",
30
+ code: "usage",
31
+ retryable: false,
32
+ request_id: crypto.randomUUID(),
33
+ error: { message, hint },
34
+ });
35
+ process.exit(EXIT.USAGE);
36
+ }
37
+ export function catchToExit(e, asJson) {
38
+ if (e instanceof GbcError) {
39
+ const r = {
40
+ schema_version: "1.0.0",
41
+ ok: false,
42
+ outcome: e.outcome,
43
+ code: e.code,
44
+ retryable: e.retryable,
45
+ request_id: crypto.randomUUID(),
46
+ error: { message: e.message, hint: e.hint, details: e.details },
47
+ };
48
+ writeErrJson(r);
49
+ return e.exitCode;
50
+ }
51
+ writeErrJson({
52
+ schema_version: "1.0.0",
53
+ ok: false,
54
+ outcome: "known_failure",
55
+ code: "error",
56
+ retryable: false,
57
+ request_id: crypto.randomUUID(),
58
+ error: { message: String(e) },
59
+ });
60
+ return EXIT.ERROR;
61
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};