gipity 1.0.429 → 1.1.2
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 +67 -29
- package/dist/agents/claude-code.js +48 -0
- package/dist/agents/codex.js +45 -0
- package/dist/agents/grok.js +50 -0
- package/dist/agents/index.js +23 -0
- package/dist/agents/types.js +18 -0
- package/dist/api.js +14 -4
- package/dist/capture/sources/codex.js +216 -0
- package/dist/capture/sources/grok.js +178 -0
- package/dist/catalog.js +46 -0
- package/dist/client-context.js +2 -0
- package/dist/commands/add.js +9 -22
- package/dist/commands/build.js +1330 -0
- package/dist/commands/chat.js +9 -3
- package/dist/commands/claude.js +4 -13
- package/dist/commands/db.js +22 -5
- package/dist/commands/deploy.js +7 -0
- package/dist/commands/doctor.js +8 -2
- package/dist/commands/fn.js +24 -1
- package/dist/commands/init.js +9 -15
- package/dist/commands/logs.js +21 -2
- package/dist/commands/page-eval.js +25 -10
- package/dist/commands/page-inspect.js +7 -3
- package/dist/commands/page-screenshot.js +62 -23
- package/dist/commands/project.js +1 -1
- package/dist/commands/relay-install.js +1 -1
- package/dist/commands/relay.js +3 -3
- package/dist/commands/sandbox.js +62 -16
- package/dist/commands/setup.js +16 -10
- package/dist/commands/status.js +35 -7
- package/dist/commands/test.js +7 -0
- package/dist/commands/uninstall.js +25 -3
- package/dist/flag-aliases.js +1 -0
- package/dist/hooks/capture-runner.js +127 -38
- package/dist/index.js +1118 -361
- package/dist/knowledge.js +3 -3
- package/dist/prefs.js +42 -0
- package/dist/project-setup.js +2 -10
- package/dist/relay/daemon.js +124 -16
- package/dist/relay/diagnostics.js +4 -2
- package/dist/relay/onboarding.js +9 -9
- package/dist/relay/setup.js +8 -8
- package/dist/relay/state.js +1 -1
- package/dist/setup.js +258 -17
- package/package.json +4 -3
package/dist/knowledge.js
CHANGED
|
@@ -54,7 +54,7 @@ Prefer the cheapest option that works - CLI and sandbox are instant and free, ap
|
|
|
54
54
|
3. App services - runtime HTTP endpoints your deployed app calls directly at \`https://a.gipity.ai/api/<PROJECT_GUID>/services/*\`. Available: LLM, TTS, image, sound, music, transcribe, video, file upload, realtime, location, push notifications (Gipity Notify - \`gipity add notify\`, send from a function with the injected \`notify()\`; see \`app-notify\`). Load the matching skill (\`app-llm\`, \`app-tts\`, etc.) before writing service code - they have the schemas, auth pattern, and common-mistake guards. For one-off generation during development, prefer \`gipity generate <image|video|speech|sound|music>\` or \`gipity chat\` - direct generation always bills you (the owner), regardless of any service \`billing_mode\` (that setting only governs the deployed app's runtime calls; never flip it just to create assets). \`gipity generate\` saves to a generic file in the current directory by default (e.g. \`./generated.png\`) - pass \`-o <path>\` to write it straight into your source tree so it deploys (e.g. \`gipity generate image "hero banner" -o src/assets/images/hero.png\`) instead of generating at cwd and moving it.
|
|
55
55
|
4. Delegate to Gip (\`gipity chat "<task>"\`) - only when the work genuinely needs agent reasoning or a tool not in the CLI, sandbox, or app services. Required for: Twitter/X search, Gmail, calendar, push notifications, video understanding, audio source isolation, cross-model second opinions, multi-step orchestration. Don't use \`gipity chat\` for anything the sandbox can do - it's slower and burns tokens.
|
|
56
56
|
|
|
57
|
-
You are the developer. Write files in this directory -
|
|
57
|
+
You are the developer. Write files in this directory - Gipity's editor hooks (installed by \`gipity init\` into Claude Code, Grok, and Codex) auto-sync them to Gipity. Don't run \`npm install\`, \`npm start\`, \`node\`, or \`python\` locally; there is no local runtime. Code runs in the Gipity sandbox.
|
|
58
58
|
|
|
59
59
|
## Use first-party services before reaching outside
|
|
60
60
|
|
|
@@ -90,7 +90,7 @@ The full "when to add a template" rule and the definition of done are spelled ou
|
|
|
90
90
|
|
|
91
91
|
Build loop: \`gipity add\` → edit files → \`gipity deploy dev --inspect\` → fix any errors → repeat until the definition of done is met. \`deploy --inspect\` deploys and then runs the page-inspect report on the live URL in one command; use a standalone \`gipity page inspect <url>\` only when re-checking without deploying.
|
|
92
92
|
|
|
93
|
-
\`add\` writes real files to disk - Read a scaffolded file before your first Write/Edit to it, or the call fails \`"File has not been read yet"
|
|
93
|
+
\`add\` writes real files to disk - Read a scaffolded file with the Read tool before your first Write/Edit to it, or the call fails \`"File has not been read yet"\` (viewing it with \`cat\`/\`head\` in Bash does not count - the harness only tracks the Read tool). Don't rewrite from memory of the template.
|
|
94
94
|
|
|
95
95
|
Make your file changes and verify they landed, then run \`gipity deploy dev\` once. \`0 uploaded, N unchanged\` means nothing changed on disk - fix the files, don't re-run deploy or probe the environment.
|
|
96
96
|
|
|
@@ -146,7 +146,7 @@ Every tool call returns its full output with that call. There is no output buffe
|
|
|
146
146
|
|
|
147
147
|
This directory is the app root - it holds \`.gipity.json\` (and \`gipity.yaml\` for backends) and is already your cwd, so run app commands here. Don't \`cd\` to a git root (\`git rev-parse --show-toplevel\`): when the app is nested in a larger repo that resolves outside the app.
|
|
148
148
|
|
|
149
|
-
Write files locally -
|
|
149
|
+
Write files locally - Gipity's editor hooks (installed into Claude Code, Grok, and Codex by \`gipity init\`) auto-push every save to Gipity and auto-pull remote changes (images, audio from \`gipity chat\`) before each turn. Use \`gipity sync\` if things get out of sync (or if the hooks aren't installed - \`gipity status --repair-hooks\` re-enables them). Deletes are safe - use \`rollback\` with a datetime to undo, or \`file_version_restore\` for individual files.
|
|
150
150
|
|
|
151
151
|
To keep local-only material (research clones, scratch data, vendored references) in the project directory without syncing or deploying it, list it in a \`.gipityignore\` at the project root - gitignore-style, one pattern per line, \`#\` comments. Ignored paths are invisible to sync in both directions; anything that already synced before being ignored stays on the server until you delete it.
|
|
152
152
|
|
package/dist/prefs.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-level user preferences (~/.gipity/prefs.json) - the `gipity build`
|
|
3
|
+
* agent picker's memory. Deliberately NOT in a project's .gipity.json:
|
|
4
|
+
* per-project storage would re-ask the agent question on every new project,
|
|
5
|
+
* which defeats "hit enter twice".
|
|
6
|
+
*
|
|
7
|
+
* Shape:
|
|
8
|
+
* { "lastAgent": "claude" }
|
|
9
|
+
*
|
|
10
|
+
* The model is never stored: `gipity build` doesn't ask, so the agent's own
|
|
11
|
+
* default (and its own model memory) stays authoritative unless the user
|
|
12
|
+
* passes --model, which we forward verbatim.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
15
|
+
import { homedir } from 'os';
|
|
16
|
+
import { join } from 'path';
|
|
17
|
+
const PREFS_PATH = join(homedir(), '.gipity', 'prefs.json');
|
|
18
|
+
export function readPrefs() {
|
|
19
|
+
if (!existsSync(PREFS_PATH))
|
|
20
|
+
return {};
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(readFileSync(PREFS_PATH, 'utf-8'));
|
|
23
|
+
return {
|
|
24
|
+
lastAgent: typeof parsed.lastAgent === 'string' ? parsed.lastAgent : undefined,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return {}; // unreadable prefs must never block a launch
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function writePrefs(update) {
|
|
32
|
+
try {
|
|
33
|
+
const current = readPrefs();
|
|
34
|
+
const next = { ...current, ...update };
|
|
35
|
+
mkdirSync(join(homedir(), '.gipity'), { recursive: true });
|
|
36
|
+
writeFileSync(PREFS_PATH, JSON.stringify(next, null, 2) + '\n');
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* best-effort - prefs are a convenience */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=prefs.js.map
|
package/dist/project-setup.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { clearConfigCache, saveConfigAt, getApiBaseOverride, DEFAULT_API_BASE } from './config.js';
|
|
8
8
|
import { sync } from './sync.js';
|
|
9
9
|
import { createProgressReporter } from './progress.js';
|
|
10
|
-
import {
|
|
10
|
+
import { setupProjectTools, DEFAULT_TOOLS, DEFAULT_SYNC_IGNORE } from './setup.js';
|
|
11
11
|
import { substituteDir } from './template-vars.js';
|
|
12
12
|
import { muted } from './colors.js';
|
|
13
13
|
/** Write `.gipity.json` in `dir`, chdir into it so the hook/skill writers
|
|
@@ -61,15 +61,7 @@ export async function finalizeLocalProject(opts) {
|
|
|
61
61
|
throw err;
|
|
62
62
|
// soft mode - swallow; caller can log
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
// Claude hooks (file-sync + capture) only matter if the Claude primer is
|
|
66
|
-
// being written. Skipping them for Codex/Gemini/Cursor-only inits avoids
|
|
67
|
-
// littering `.claude/settings.json` into projects that don't use Claude.
|
|
68
|
-
if (tools.some(t => t.key === 'claude'))
|
|
69
|
-
setupClaudeHooks();
|
|
70
|
-
for (const t of tools)
|
|
71
|
-
t.setup();
|
|
72
|
-
setupGitignore();
|
|
64
|
+
setupProjectTools(opts.tools ?? DEFAULT_TOOLS);
|
|
73
65
|
return { applied };
|
|
74
66
|
}
|
|
75
67
|
//# sourceMappingURL=project-setup.js.map
|
package/dist/relay/daemon.js
CHANGED
|
@@ -6,7 +6,7 @@ import { homedir, hostname, platform as osPlatform, loadavg, freemem, totalmem,
|
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import { getApiBaseOverride, DEFAULT_API_BASE } from '../config.js';
|
|
8
8
|
import { getProjectsRoot } from './paths.js';
|
|
9
|
-
import {
|
|
9
|
+
import { setupProjectTools, DEFAULT_SYNC_IGNORE } from '../setup.js';
|
|
10
10
|
import { getAuth, readAuthFresh, refreshTokenIfNeeded, accessTokenExpired } from '../auth.js';
|
|
11
11
|
import { post } from '../api.js';
|
|
12
12
|
import * as state from './state.js';
|
|
@@ -24,7 +24,8 @@ import { SessionPool, PoolFullError } from './session-pool.js';
|
|
|
24
24
|
import { getConfig } from '../config.js';
|
|
25
25
|
import { getAccountSlug } from '../api.js';
|
|
26
26
|
import { buildFreshWrap, buildResumeWrap, buildProjectContextBlock } from '../prompts.js';
|
|
27
|
-
import { fetchProjectStats } from '../commands/
|
|
27
|
+
import { fetchProjectStats } from '../commands/build.js';
|
|
28
|
+
import { getAdapterBySource } from '../agents/index.js';
|
|
28
29
|
// Re-exported so the existing `relay-bridge-abort.test.ts` keeps working.
|
|
29
30
|
// New callers should import from device-http.js directly.
|
|
30
31
|
export const bridgeAbort = bridgeAbortImpl;
|
|
@@ -949,6 +950,15 @@ async function handleDispatch(claimed) {
|
|
|
949
950
|
if (!syncFailed)
|
|
950
951
|
pushSystem(bootstrapped ? 'Project files synced.' : 'Attached files synced.');
|
|
951
952
|
}
|
|
953
|
+
// Which agent runs this dispatch. Older servers omit remote_type - they
|
|
954
|
+
// only ever dispatched Claude Code.
|
|
955
|
+
const adapter = getAdapterBySource(d.remote_type ?? 'claude_code');
|
|
956
|
+
// Non-Claude agents take the simpler hook-capture dispatch path: no
|
|
957
|
+
// stream-json ingest, no session pool.
|
|
958
|
+
if (adapter.key !== 'claude') {
|
|
959
|
+
await handleNonClaudeDispatch(adapter, d, cwd, queue, pushSystem, flushQueue);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
952
962
|
// Phase 2: if the session pool is enabled, try to run this turn in a
|
|
953
963
|
// long-lived Claude Code session (fast follow-up + clean interrupt). Any
|
|
954
964
|
// failure - pool saturated, SDK error, wrap failure - falls through to the
|
|
@@ -958,8 +968,11 @@ async function handleDispatch(claimed) {
|
|
|
958
968
|
if (handled)
|
|
959
969
|
return;
|
|
960
970
|
}
|
|
961
|
-
// Build argv for `gipity claude -p …` (or with --resume).
|
|
962
|
-
// as array so the message string can't be interpreted as
|
|
971
|
+
// Build argv for `gipity build --agent claude -p …` (or with --resume).
|
|
972
|
+
// No shell - argv as array so the message string can't be interpreted as
|
|
973
|
+
// shell syntax. (`build` is the renamed launcher; the flags below pass
|
|
974
|
+
// through it to the `claude` binary unchanged, exactly as the legacy
|
|
975
|
+
// `gipity claude -p` spawn did.)
|
|
963
976
|
//
|
|
964
977
|
// `--permission-mode bypassPermissions`: a relay dispatch has no
|
|
965
978
|
// human on the other end to click "Approve" - Claude prompting would
|
|
@@ -967,7 +980,7 @@ async function handleDispatch(claimed) {
|
|
|
967
980
|
// the device and dispatching the message; skipping the interactive
|
|
968
981
|
// prompt is correct (same authority as running `claude -p` in a local
|
|
969
982
|
// terminal yourself).
|
|
970
|
-
const args = ['claude', '-p', d.message, '--permission-mode', 'bypassPermissions'];
|
|
983
|
+
const args = ['build', '--agent', 'claude', '-p', d.message, '--permission-mode', 'bypassPermissions'];
|
|
971
984
|
// Relay sessions run in -p mode, where Claude Code's AskUserQuestion tool
|
|
972
985
|
// is unavailable - so without help the model would ask clarifying
|
|
973
986
|
// questions as prose the user just types back. This system-prompt
|
|
@@ -984,7 +997,7 @@ async function handleDispatch(claimed) {
|
|
|
984
997
|
if (d.kind === 'resume' && d.remote_session_id) {
|
|
985
998
|
args.push('--resume', d.remote_session_id);
|
|
986
999
|
}
|
|
987
|
-
log('debug', 'spawning gipity claude', {
|
|
1000
|
+
log('debug', 'spawning gipity build --agent claude', {
|
|
988
1001
|
id: d.short_guid,
|
|
989
1002
|
cwd,
|
|
990
1003
|
args,
|
|
@@ -1123,7 +1136,92 @@ async function handleDispatch(claimed) {
|
|
|
1123
1136
|
}
|
|
1124
1137
|
else {
|
|
1125
1138
|
log('warn', 'dispatch child exited nonzero', { id: d.short_guid, exitCode, ms });
|
|
1126
|
-
await ack(d.short_guid, 'error', `
|
|
1139
|
+
await ack(d.short_guid, 'error', `Claude Code exited with code ${exitCode}${stderrNote}`);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Dispatch for non-Claude agents (Codex, Grok). Deliberately simpler than
|
|
1144
|
+
* the Claude path: there is no stream-json ingest mapper for these agents -
|
|
1145
|
+
* hook-based session capture (Phase C) owns the transcript. The spawned
|
|
1146
|
+
* `gipity build --agent <key> -p …` child gets GIPITY_CONVERSATION_GUID (so
|
|
1147
|
+
* capture posts into the right conversation) and capture is NOT turned off.
|
|
1148
|
+
* The daemon contributes: the "Running <agent>" marker, byte-level progress
|
|
1149
|
+
* heartbeats (spawnGipityClaude's generic plumbing with streamJson:false),
|
|
1150
|
+
* post-run file sync, the tail marker, and the ack. The prompt entry itself
|
|
1151
|
+
* arrives via capture from the agent's own transcript - the daemon doesn't
|
|
1152
|
+
* push one, or it would render twice.
|
|
1153
|
+
*/
|
|
1154
|
+
async function handleNonClaudeDispatch(adapter, d, cwd, queue, pushSystem, flushQueue) {
|
|
1155
|
+
// `gipity build` translates these into the agent's own argv via the same
|
|
1156
|
+
// adapter (Codex: `exec …`; Grok: `-p … --always-approve`). Raw message -
|
|
1157
|
+
// AGENTS.md carries the Gipity context for these agents; no wrapping.
|
|
1158
|
+
const args = ['build', '--agent', adapter.key, '-p', d.message, '--bypass-approvals'];
|
|
1159
|
+
if (d.model)
|
|
1160
|
+
args.push('--model', d.model);
|
|
1161
|
+
if (d.kind === 'resume' && d.remote_session_id)
|
|
1162
|
+
args.push('--resume', d.remote_session_id);
|
|
1163
|
+
log('debug', `spawning gipity build --agent ${adapter.key}`, {
|
|
1164
|
+
id: d.short_guid, cwd, args, conv: d.conversation_guid,
|
|
1165
|
+
chain: d.kind === 'resume' ? `resume ${d.remote_session_id}` : 'start (fresh session)',
|
|
1166
|
+
});
|
|
1167
|
+
const words = d.message.trim().split(/\s+/).filter(Boolean).length;
|
|
1168
|
+
pushSystem(`Running ${adapter.displayName} - ${words.toLocaleString('en-US')} words`);
|
|
1169
|
+
const t0 = Date.now();
|
|
1170
|
+
let exitCode = 1;
|
|
1171
|
+
let spawnErr = null;
|
|
1172
|
+
let killed = false;
|
|
1173
|
+
let runtimeLimit = false;
|
|
1174
|
+
let stderrTail = '';
|
|
1175
|
+
try {
|
|
1176
|
+
const result = await spawnGipityClaude(args, cwd, d, queue, { streamJson: false });
|
|
1177
|
+
exitCode = result.exitCode;
|
|
1178
|
+
killed = result.killed;
|
|
1179
|
+
runtimeLimit = result.runtimeLimit ?? false;
|
|
1180
|
+
stderrTail = result.stderrTail ?? '';
|
|
1181
|
+
}
|
|
1182
|
+
catch (err) {
|
|
1183
|
+
spawnErr = err?.message || String(err);
|
|
1184
|
+
log('error', 'dispatch spawn failed', { id: d.short_guid, err: spawnErr });
|
|
1185
|
+
}
|
|
1186
|
+
const dur = formatDuration(Date.now() - t0);
|
|
1187
|
+
// Same post-run reconcile as the Claude path: push files the agent wrote
|
|
1188
|
+
// via Bash/scripts back to VFS before the ack (hooks only cover its
|
|
1189
|
+
// native edit tools). Skipped on kill - the replacement dispatch owns the tree.
|
|
1190
|
+
if (!spawnErr && !killed) {
|
|
1191
|
+
try {
|
|
1192
|
+
await spawnSync(cwd, PROJECT_SYNC_TIMEOUT_MS);
|
|
1193
|
+
}
|
|
1194
|
+
catch (err) {
|
|
1195
|
+
log('warn', 'sync after dispatch failed', { id: d.short_guid, err: err?.message });
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
const stderrNote = stderrTail ? `: ${stderrTail.slice(0, 300)}` : '';
|
|
1199
|
+
const tail = runtimeLimit
|
|
1200
|
+
? `stopped after ${dur} (runtime limit)`
|
|
1201
|
+
: killed
|
|
1202
|
+
? `cancelled (${dur})`
|
|
1203
|
+
: spawnErr
|
|
1204
|
+
? `failed (${dur}: ${spawnErr})`
|
|
1205
|
+
: exitCode === 0
|
|
1206
|
+
? `finished (${dur})`
|
|
1207
|
+
: `failed (${dur}, exit ${exitCode}${stderrNote})`;
|
|
1208
|
+
pushSystem(`${adapter.displayName} ${tail}`);
|
|
1209
|
+
await flushQueue();
|
|
1210
|
+
if (runtimeLimit) {
|
|
1211
|
+
await ack(d.short_guid, 'error', `${adapter.displayName} stopped after ${dur} (runtime limit)`);
|
|
1212
|
+
}
|
|
1213
|
+
else if (killed) {
|
|
1214
|
+
await ack(d.short_guid, 'cancelled');
|
|
1215
|
+
}
|
|
1216
|
+
else if (spawnErr) {
|
|
1217
|
+
await ack(d.short_guid, 'error', spawnErr);
|
|
1218
|
+
}
|
|
1219
|
+
else if (exitCode === 0) {
|
|
1220
|
+
log('info', 'dispatch done', { id: d.short_guid, agent: adapter.key });
|
|
1221
|
+
await ack(d.short_guid, 'done');
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
await ack(d.short_guid, 'error', `${adapter.displayName} exited with code ${exitCode}${stderrNote}`);
|
|
1127
1225
|
}
|
|
1128
1226
|
}
|
|
1129
1227
|
/**
|
|
@@ -1372,10 +1470,7 @@ async function resolveCwdForProject(d) {
|
|
|
1372
1470
|
const origCwd = process.cwd();
|
|
1373
1471
|
try {
|
|
1374
1472
|
process.chdir(path);
|
|
1375
|
-
|
|
1376
|
-
setupClaudeMd();
|
|
1377
|
-
setupAgentsMd();
|
|
1378
|
-
setupGitignore();
|
|
1473
|
+
setupProjectTools();
|
|
1379
1474
|
}
|
|
1380
1475
|
finally {
|
|
1381
1476
|
process.chdir(origCwd);
|
|
@@ -1562,6 +1657,11 @@ async function spawnSync(cwd, timeoutMs, onSpawn) {
|
|
|
1562
1657
|
});
|
|
1563
1658
|
}
|
|
1564
1659
|
export async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
1660
|
+
// streamJson (default true) = the Claude path: parse the child's
|
|
1661
|
+
// stream-json stdout into ingest entries and stand hook capture down.
|
|
1662
|
+
// false = non-Claude agents (Codex/Grok): hook capture owns the
|
|
1663
|
+
// transcript; stdout only feeds the byte-level progress heartbeat.
|
|
1664
|
+
const streamJson = meta?.streamJson !== false;
|
|
1565
1665
|
// resolveCommand: on Windows the bare `gipity` is a .cmd shim that spawn
|
|
1566
1666
|
// can't launch without an explicit path. An explicit env override is used
|
|
1567
1667
|
// verbatim (it may be a full path); only the default name is resolved.
|
|
@@ -1572,13 +1672,19 @@ export async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
1572
1672
|
// `--include-partial-messages` adds stream_event token deltas, which
|
|
1573
1673
|
// feed the ephemeral live-typing channel (see stream-delta.ts); whole
|
|
1574
1674
|
// assistant/tool events still arrive unchanged for the persistent path.
|
|
1575
|
-
const fullArgs =
|
|
1675
|
+
const fullArgs = streamJson
|
|
1676
|
+
? [...args, '--output-format', 'stream-json', '--verbose', '--include-partial-messages']
|
|
1677
|
+
: [...args];
|
|
1576
1678
|
// GIPITY_CAPTURE=off: the daemon owns capture for this dispatch (it
|
|
1577
1679
|
// parses the stream-json on stdout), so the plugin's lifecycle-hook
|
|
1578
1680
|
// capture must stand down. `gipity claude` sets the same sentinel on the
|
|
1579
1681
|
// Claude child when it detects a daemon spawn; setting it here too keeps
|
|
1580
|
-
// dispatches double-post-free even across CLI version skew.
|
|
1581
|
-
|
|
1682
|
+
// dispatches double-post-free even across CLI version skew. Non-Claude
|
|
1683
|
+
// agents are the opposite: hook capture IS the transcript path, so the
|
|
1684
|
+
// sentinel must NOT be set.
|
|
1685
|
+
const env = streamJson
|
|
1686
|
+
? childEnv({ GIPITY_CONVERSATION_GUID: d.conversation_guid, GIPITY_CAPTURE: 'off' })
|
|
1687
|
+
: childEnv({ GIPITY_CONVERSATION_GUID: d.conversation_guid });
|
|
1582
1688
|
return new Promise((resolve, reject) => {
|
|
1583
1689
|
const child = spawnCommand(cmd, fullArgs, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1584
1690
|
// `exited` fires when the child fully unwinds (exit event). Callers
|
|
@@ -1780,9 +1886,11 @@ export async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
1780
1886
|
child.stdout?.on('data', (chunk) => {
|
|
1781
1887
|
stdoutBytesTotal += chunk.length;
|
|
1782
1888
|
lastStdoutByteAt = Date.now();
|
|
1783
|
-
|
|
1889
|
+
if (streamJson)
|
|
1890
|
+
splitter.push(chunk);
|
|
1784
1891
|
});
|
|
1785
|
-
child.stdout?.on('end', () =>
|
|
1892
|
+
child.stdout?.on('end', () => { if (streamJson)
|
|
1893
|
+
splitter.flush(); });
|
|
1786
1894
|
// Stderr: human-readable only (Claude's progress bars, errors).
|
|
1787
1895
|
// Kept on the daemon's own stderr for `gipity relay log`, plus a
|
|
1788
1896
|
// small tail ring so a crash with no stream output can include the
|
|
@@ -137,14 +137,16 @@ export async function collectDiagnostics() {
|
|
|
137
137
|
} })();
|
|
138
138
|
// Run the subprocess probes concurrently (each already bounded + best-effort)
|
|
139
139
|
// so the whole snapshot costs one timeout, not the sum of four.
|
|
140
|
-
const [claude, codex, cursor, gpu] = await Promise.all([
|
|
141
|
-
probeVersion('claude'), probeVersion('codex'), probeVersion('cursor'), detectGpu(),
|
|
140
|
+
const [claude, codex, grok, cursor, gpu] = await Promise.all([
|
|
141
|
+
probeVersion('claude'), probeVersion('codex'), probeVersion('grok'), probeVersion('cursor'), detectGpu(),
|
|
142
142
|
]);
|
|
143
143
|
const agents = {};
|
|
144
144
|
if (claude)
|
|
145
145
|
agents.claude_code = claude;
|
|
146
146
|
if (codex)
|
|
147
147
|
agents.codex = codex;
|
|
148
|
+
if (grok)
|
|
149
|
+
agents.grok = grok;
|
|
148
150
|
if (cursor)
|
|
149
151
|
agents.cursor = cursor;
|
|
150
152
|
return {
|
package/dist/relay/onboarding.js
CHANGED
|
@@ -75,7 +75,7 @@ export async function runRelaySetup(opts) {
|
|
|
75
75
|
console.log(` ${dim('To register this computer again — for example under a different name —')}`);
|
|
76
76
|
console.log(` ${dim('unregister it first, then re-run setup:')}`);
|
|
77
77
|
console.log(` ${brand('gipity relay revoke')} ${dim('# unpairs this computer and removes the login service')}`);
|
|
78
|
-
console.log(` ${brand('gipity
|
|
78
|
+
console.log(` ${brand('gipity connect')} ${dim('# register it again (asks for a new name)')}`);
|
|
79
79
|
console.log('');
|
|
80
80
|
}
|
|
81
81
|
return true;
|
|
@@ -84,14 +84,14 @@ export async function runRelaySetup(opts) {
|
|
|
84
84
|
// `gipity claude` first-run frames it as an optional add-on it's offering.
|
|
85
85
|
if (opts.mode === 'run-now') {
|
|
86
86
|
console.log(` ${bold('Set up this computer as a relay')}`);
|
|
87
|
-
console.log(` ${dim('A relay runs Claude Code
|
|
88
|
-
console.log(` ${dim('It uses your Claude or
|
|
87
|
+
console.log(` ${dim('A relay runs your coding agent (Claude Code, Codex, or Grok) here so you can drive it from the web (')}${brand('gipity.ai')}${dim(') on any browser.')}`);
|
|
88
|
+
console.log(` ${dim('It uses your Claude, Codex, or Grok subscription — the cheapest way to pay for tokens.')}`);
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
|
-
console.log(` ${bold('Remote control of
|
|
92
|
-
console.log(` ${dim('Drive this
|
|
91
|
+
console.log(` ${bold('Remote control of your coding agent')}`);
|
|
92
|
+
console.log(` ${dim('Drive your coding agent on this computer from the web (')}${brand('gipity.ai')}${dim(') on any browser (desktop or phone).')}`);
|
|
93
93
|
console.log('');
|
|
94
|
-
console.log(` ${dim('Enable now (takes 2 seconds) or turn on later with')} ${brand('gipity
|
|
94
|
+
console.log(` ${dim('Enable now (takes 2 seconds) or turn on later with')} ${brand('gipity connect')}`);
|
|
95
95
|
}
|
|
96
96
|
console.log('');
|
|
97
97
|
const promptText = opts.mode === 'run-now'
|
|
@@ -121,7 +121,7 @@ export async function runRelaySetup(opts) {
|
|
|
121
121
|
}
|
|
122
122
|
catch (err) {
|
|
123
123
|
console.error(`\n ${clrError(`Could not create device: ${err?.message || err}`)}`);
|
|
124
|
-
console.error(` ${dim('Skipping for now - we\'ll offer again next time. Or turn it on with `gipity
|
|
124
|
+
console.error(` ${dim('Skipping for now - we\'ll offer again next time. Or turn it on with `gipity connect`.')}`);
|
|
125
125
|
// Deliberately DON'T persist relay_enabled here: the user SAID YES, and
|
|
126
126
|
// this is a transient failure (network blip, server down). Leaving the
|
|
127
127
|
// tri-state unset means onboarding re-offers on the next `gipity claude`
|
|
@@ -129,7 +129,7 @@ export async function runRelaySetup(opts) {
|
|
|
129
129
|
return false;
|
|
130
130
|
}
|
|
131
131
|
// Start the daemon for this session.
|
|
132
|
-
const startNow = await confirm(' Start the relay now (and on future `gipity
|
|
132
|
+
const startNow = await confirm(' Start the relay now (and on future `gipity build` runs)?', { default: 'yes' });
|
|
133
133
|
if (startNow) {
|
|
134
134
|
startDaemon();
|
|
135
135
|
}
|
|
@@ -145,7 +145,7 @@ export async function runRelaySetup(opts) {
|
|
|
145
145
|
// generic non-zero. The relay itself is unaffected (it's already
|
|
146
146
|
// running this session, and `gipity claude` restarts it).
|
|
147
147
|
console.log(` ${muted('Auto-start needs systemd, which this WSL distro has off. The relay still runs -')}`);
|
|
148
|
-
console.log(` ${muted('it started just now and `gipity
|
|
148
|
+
console.log(` ${muted('it started just now and `gipity build` restarts it. For boot-time auto-start:')}`);
|
|
149
149
|
console.log(` ${muted('add [boot] systemd=true to /etc/wsl.conf, restart WSL, then run')} ${brand('gipity relay install')}${muted('.')}`);
|
|
150
150
|
}
|
|
151
151
|
else {
|
package/dist/relay/setup.js
CHANGED
|
@@ -60,8 +60,10 @@ export function resolveCliPath() {
|
|
|
60
60
|
/**
|
|
61
61
|
* Create + persist a relay device, marking the relay opted-in. Idempotent
|
|
62
62
|
* unless `force`: if this machine is already paired and `!force`, the
|
|
63
|
-
* existing device is returned untouched (no server call). With `force`,
|
|
64
|
-
*
|
|
63
|
+
* existing device is returned untouched (no server call). With `force`, we
|
|
64
|
+
* simply re-register: the server's machine_id dedup reattaches the same
|
|
65
|
+
* device row and rotates its token (no pre-revoke - revoking first forced a
|
|
66
|
+
* NEW row and orphaned the conversations bound to the old one, issue #291).
|
|
65
67
|
*
|
|
66
68
|
* Throws on a bad name or if the `/remote-devices` call fails (e.g. 401 when
|
|
67
69
|
* the user isn't logged in) — callers translate that into their own UX.
|
|
@@ -78,12 +80,10 @@ export async function pairDevice(opts = {}) {
|
|
|
78
80
|
};
|
|
79
81
|
}
|
|
80
82
|
if (existing && opts.force) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Best-effort: a stale server-side device is harmless; proceed to re-pair.
|
|
86
|
-
}
|
|
83
|
+
// Re-register in place: POST /remote-devices with this machine_id
|
|
84
|
+
// reattaches the existing row and rotates the token, which invalidates
|
|
85
|
+
// the old bearer anyway - a separate revoke round-trip only detached
|
|
86
|
+
// the row and orphaned its conversations.
|
|
87
87
|
state.clearDevice();
|
|
88
88
|
}
|
|
89
89
|
const name = (opts.name?.trim() || friendlyDeviceName()).trim();
|
package/dist/relay/state.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* paused: boolean,
|
|
9
9
|
* }
|
|
10
10
|
*
|
|
11
|
-
* The `token` field is the raw device bearer returned by /
|
|
11
|
+
* The `token` field is the raw device bearer returned by POST /remote-devices - it
|
|
12
12
|
* never leaves this file or the Authorization header. A future chunk will
|
|
13
13
|
* move it to OS keychain (macOS Keychain, libsecret, wincred); the state
|
|
14
14
|
* module's public surface is designed to absorb that change.
|