gipity 1.0.410 → 1.0.412
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/dist/commands/claude.js +36 -43
- package/dist/commands/credits.js +14 -9
- package/dist/commands/setup.js +59 -0
- package/dist/hooks/capture-runner.js +15 -2
- package/dist/index.js +3 -1
- package/dist/login-flow.js +42 -0
- package/dist/relay/daemon.js +445 -67
- package/dist/relay/ingest-queue.js +148 -0
- package/dist/relay/onboarding.js +51 -23
- package/dist/relay/redact.js +1 -1
- package/dist/relay/stream-delta.js +235 -0
- package/dist/relay/stream-json.js +61 -15
- package/dist/setup.js +14 -4
- package/package.json +2 -2
package/dist/commands/claude.js
CHANGED
|
@@ -4,15 +4,16 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, readdir
|
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { resolveCommand, spawnCommand } from '../platform.js';
|
|
7
|
-
import { getAuth,
|
|
8
|
-
import { get, post,
|
|
7
|
+
import { getAuth, sessionExpired, accessTokenExpired, refreshTokenIfNeeded } from '../auth.js';
|
|
8
|
+
import { get, post, ApiError, getAccountSlug } from '../api.js';
|
|
9
|
+
import { interactiveLogin } from '../login-flow.js';
|
|
9
10
|
import { getConfig, saveConfigAt, clearConfigCache, getApiBaseOverride, DEFAULT_API_BASE, getConfigPath } from '../config.js';
|
|
10
11
|
import { sync } from '../sync.js';
|
|
11
12
|
import { slugify, setupClaudeHooks, ensureGipityPluginInstalled, setupClaudeMd, setupAgentsMd, setupGitignore, DEFAULT_SYNC_IGNORE, isSyncIgnored } from '../setup.js';
|
|
12
13
|
import { buildProjectContextBlock as buildProjectContextBlockText, buildNewProjectPrompt, buildResumeWrap, buildFreshWrap, } from '../prompts.js';
|
|
13
14
|
import * as relayState from '../relay/state.js';
|
|
14
15
|
import { maybeOfferRelayOn, ensureDaemonRunning } from '../relay/onboarding.js';
|
|
15
|
-
import { prompt, pickOne,
|
|
16
|
+
import { prompt, pickOne, confirm } from '../utils.js';
|
|
16
17
|
import { brand, bold, info, success, warning, error as clrError, muted } from '../colors.js';
|
|
17
18
|
import { createProgressReporter } from '../progress.js';
|
|
18
19
|
import { printBanner } from '../banner.js';
|
|
@@ -118,33 +119,9 @@ async function buildProjectContextBlock(opts) {
|
|
|
118
119
|
const stats = await fetchProjectStats(opts.projectGuid, opts.cwd);
|
|
119
120
|
return buildProjectContextBlockText({ ...opts, ...stats });
|
|
120
121
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
async function interactiveLogin() {
|
|
125
|
-
const email = await prompt(' Email: ');
|
|
126
|
-
if (!email) {
|
|
127
|
-
console.error(`\n ${clrError('Email required.')}`);
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
await publicPost('/auth/login', { email });
|
|
131
|
-
console.log(' Check your email for a 6-digit code.\n');
|
|
132
|
-
const code = await prompt(' Code: ');
|
|
133
|
-
if (!code) {
|
|
134
|
-
console.error(`\n ${clrError('Code required.')}`);
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
|
-
const res = await publicPost('/auth/verify', { email, code });
|
|
138
|
-
const exp = decodeJwtExp(res.accessToken);
|
|
139
|
-
if (!exp) {
|
|
140
|
-
console.error(`\n ${clrError('Invalid token received.')}`);
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
const expiresAt = new Date(exp * 1000).toISOString();
|
|
144
|
-
saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
|
|
145
|
-
console.log(` ${success(`Logged in (${email}).`)}`);
|
|
146
|
-
return getAuth();
|
|
147
|
-
}
|
|
122
|
+
// Interactive email+code login now lives in `login-flow.ts` (shared with
|
|
123
|
+
// `gipity setup`). Used on first login and when the server returns 401
|
|
124
|
+
// mid-command (session expired).
|
|
148
125
|
// First-run relay onboarding now lives in `relay/onboarding.ts`
|
|
149
126
|
// (`maybeOfferRelayOn`). `gipity claude` invokes it after project
|
|
150
127
|
// selection, and also calls `ensureDaemonRunning` unconditionally before
|
|
@@ -310,26 +287,42 @@ export const claudeCommand = new Command('claude')
|
|
|
310
287
|
// saved session's expiry is irrelevant when one is set — never re-login or
|
|
311
288
|
// warn about expiry in that case.
|
|
312
289
|
const hasEnvToken = Boolean(process.env.GIPITY_TOKEN?.trim());
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
// can look valid locally (refresh token not past its JWT
|
|
316
|
-
// sessionExpired() is false) yet be dead on the server
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
|
|
290
|
+
// Renew the access token UP FRONT so the login line we're about to print is
|
|
291
|
+
// actually true — in BOTH interactive and headless/relay runs. A saved
|
|
292
|
+
// session can look valid locally (refresh token not past its 7-day JWT
|
|
293
|
+
// expiry, so sessionExpired() is false) yet be dead on the server: a sibling
|
|
294
|
+
// process sharing this auth.json already consumed the single-use refresh
|
|
295
|
+
// token (GipRunner and the relay run many gipity processes against one auth
|
|
296
|
+
// dir), the user logged in elsewhere, or — in dev — the server's session
|
|
297
|
+
// store was flushed/restarted. refreshTokenIfNeeded() round-trips to the
|
|
298
|
+
// server and adopts any freshly-rotated token; if it fails, the access token
|
|
299
|
+
// stays expired and accessTokenExpired() catches it below. Headless runs
|
|
300
|
+
// USED to skip this and print "Logged in" off the cheap local check alone —
|
|
301
|
+
// then the very next sync would 401 with "Session expired" and the relay
|
|
302
|
+
// would run Claude anyway against a tree it could neither pull nor push.
|
|
303
|
+
if (auth && !hasEnvToken && !sessionExpired()) {
|
|
322
304
|
await refreshTokenIfNeeded();
|
|
323
305
|
auth = getAuth();
|
|
324
306
|
}
|
|
325
307
|
// Re-login is genuinely required when the refresh token has lapsed, OR the
|
|
326
308
|
// proactive renewal above could not produce a still-valid access token
|
|
327
|
-
// (refresh token rejected
|
|
328
|
-
//
|
|
329
|
-
//
|
|
309
|
+
// (refresh token rejected — rotated away, revoked, or the server forgot it).
|
|
310
|
+
// Checked identically for headless and interactive now: a dead session must
|
|
311
|
+
// not silently proceed in either. Never triggered while a GIPITY_TOKEN env
|
|
330
312
|
// token is supplying auth.
|
|
331
313
|
const reloginRequired = auth != null && !hasEnvToken &&
|
|
332
|
-
(sessionExpired() ||
|
|
314
|
+
(sessionExpired() || accessTokenExpired());
|
|
315
|
+
if (auth && reloginRequired && nonInteractive) {
|
|
316
|
+
// Headless (relay dispatch, CI): we can't prompt for a re-login, and
|
|
317
|
+
// running Claude now would work against a project tree we can neither pull
|
|
318
|
+
// nor push (every authenticated call 401s). Fail fast with the actionable
|
|
319
|
+
// message instead of printing "Logged in" and silently losing the user's
|
|
320
|
+
// changes. The relay surfaces this stderr line to the web CLI as the
|
|
321
|
+
// dispatch error, so the user sees "run: gipity login" instead of a run
|
|
322
|
+
// that appeared to work but synced nothing.
|
|
323
|
+
console.error(` ${clrError('Your Gipity session has expired. Run: gipity login')}`);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
333
326
|
if (auth && reloginRequired) {
|
|
334
327
|
// The saved session is dead (refresh token lapsed, or rotated away by a
|
|
335
328
|
// sibling process), so the first API call below would 401. Re-login up
|
package/dist/commands/credits.js
CHANGED
|
@@ -145,13 +145,17 @@ creditsCommand
|
|
|
145
145
|
// commander binds to the parent - read merged opts so --json is seen here.
|
|
146
146
|
.action((_opts, cmd) => run('Plans', async () => {
|
|
147
147
|
const opts = cmd.optsWithGlobals();
|
|
148
|
-
|
|
148
|
+
// Plans + subscription are required; credit packs are best-effort - the plan
|
|
149
|
+
// comparison must still render if /credits/products is unavailable (e.g.
|
|
150
|
+
// credit packs aren't configured in this environment).
|
|
151
|
+
const [plansRes, subRes] = await Promise.all([
|
|
149
152
|
get('/plans'),
|
|
150
|
-
get('/credits/products'),
|
|
151
153
|
get('/credits/subscription'),
|
|
152
154
|
]);
|
|
155
|
+
const products = await get('/credits/products')
|
|
156
|
+
.then(r => r.data).catch(() => []);
|
|
153
157
|
if (opts.json) {
|
|
154
|
-
console.log(JSON.stringify({ currentTier: subRes.data.tier, plans: plansRes.data, products
|
|
158
|
+
console.log(JSON.stringify({ currentTier: subRes.data.tier, plans: plansRes.data, products }));
|
|
155
159
|
return;
|
|
156
160
|
}
|
|
157
161
|
const currentTier = subRes.data.tier;
|
|
@@ -171,7 +175,7 @@ creditsCommand
|
|
|
171
175
|
renderLimits(plan.limits, ' ');
|
|
172
176
|
console.log('');
|
|
173
177
|
}
|
|
174
|
-
const packs =
|
|
178
|
+
const packs = products.filter(p => p.type === 'one_time');
|
|
175
179
|
if (packs.length > 0) {
|
|
176
180
|
console.log(bold('Credit packs') + muted(' (require an active Pro subscription)'));
|
|
177
181
|
for (const pack of packs) {
|
|
@@ -195,11 +199,12 @@ creditsCommand
|
|
|
195
199
|
// commander binds to the parent - read merged opts so --json/--open are seen here.
|
|
196
200
|
.action((target, _opts, cmd) => run('Buy', async () => {
|
|
197
201
|
const opts = cmd.optsWithGlobals();
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const products =
|
|
202
|
+
const subRes = await get('/credits/subscription');
|
|
203
|
+
// Products are best-effort: if the catalog is unavailable we can't build a
|
|
204
|
+
// direct checkout link, so we fall back to the pricing page below rather
|
|
205
|
+
// than erroring out.
|
|
206
|
+
const products = await get('/credits/products')
|
|
207
|
+
.then(r => r.data).catch(() => []);
|
|
203
208
|
const currentTier = subRes.data.tier;
|
|
204
209
|
// Resolve the target product. With no arg, default to the first available
|
|
205
210
|
// paid subscription (the upgrade path). With an arg, match a plan/pack by
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gipity setup` — get this computer ready as a relay, then stop.
|
|
3
|
+
*
|
|
4
|
+
* Runs the SAME first steps as `gipity claude` (log in, then pair + start the
|
|
5
|
+
* relay + install the OS login service) but does NOT pick a project or launch
|
|
6
|
+
* Claude Code. For the user who wants their machine available as a relay in the
|
|
7
|
+
* web CLI without being dropped into a coding session.
|
|
8
|
+
*
|
|
9
|
+
* Shares one implementation with `gipity claude`: auth via `login-flow.ts`,
|
|
10
|
+
* relay setup via `relay/onboarding.ts` (`runRelaySetup`). Nothing to maintain
|
|
11
|
+
* twice.
|
|
12
|
+
*/
|
|
13
|
+
import { Command } from 'commander';
|
|
14
|
+
import { getAuth, sessionExpired, refreshTokenIfNeeded } from '../auth.js';
|
|
15
|
+
import { interactiveLogin } from '../login-flow.js';
|
|
16
|
+
import { runRelaySetup } from '../relay/onboarding.js';
|
|
17
|
+
import * as relayState from '../relay/state.js';
|
|
18
|
+
import { bold, brand, success, muted, error as clrError } from '../colors.js';
|
|
19
|
+
export const setupCommand = new Command('setup')
|
|
20
|
+
.description('Set up this computer as a relay (no project, no launch)')
|
|
21
|
+
.action(async () => {
|
|
22
|
+
try {
|
|
23
|
+
console.log('');
|
|
24
|
+
console.log(` ${bold('Gipity setup')} ${muted('- get this computer ready as a relay')}`);
|
|
25
|
+
console.log('');
|
|
26
|
+
// ── Step 1: Auth ──────────────────────────────────────────────────
|
|
27
|
+
let auth = getAuth();
|
|
28
|
+
if (auth && !sessionExpired()) {
|
|
29
|
+
await refreshTokenIfNeeded();
|
|
30
|
+
auth = getAuth();
|
|
31
|
+
console.log(` Logged in (${auth?.email}).`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (auth)
|
|
35
|
+
console.log(` ${muted('Your session expired. Let\'s sign you back in.')}\n`);
|
|
36
|
+
else
|
|
37
|
+
console.log(' Let\'s get you logged in.\n');
|
|
38
|
+
auth = await interactiveLogin();
|
|
39
|
+
}
|
|
40
|
+
console.log('');
|
|
41
|
+
// ── Step 2: Relay setup (always run — the user asked for it) ───────
|
|
42
|
+
const enabled = await runRelaySetup({ mode: 'run-now' });
|
|
43
|
+
// ── Step 3: Done. No project, no Claude Code launch. ──────────────
|
|
44
|
+
if (enabled) {
|
|
45
|
+
const running = relayState.isRelayEnabled() && !relayState.isPaused();
|
|
46
|
+
console.log(` ${success('Done')} — your relay ${running ? 'is running in the background' : 'is set up'} and will start with your computer.`);
|
|
47
|
+
console.log(` ${muted('Open')} ${brand('gipity.ai')} ${muted('and start a chat to drive Claude Code here. Manage it with `gipity relay status`.')}`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(` ${muted('No relay set up. Run `gipity setup` again anytime, or `gipity claude` to build with Gipity.')}`);
|
|
51
|
+
}
|
|
52
|
+
console.log('');
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
console.error(`\n ${clrError(`Error: ${err.message}`)}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Not a user-facing `gipity` subcommand by design: users never invoke
|
|
9
9
|
* this directly. The Gipity Claude Code plugin's capture hook script
|
|
10
|
-
* (
|
|
10
|
+
* (skills repo hooks/scripts/capture.cjs) resolves this file inside the
|
|
11
11
|
* installed CLI at fire time and runs it - so the capture logic versions
|
|
12
12
|
* with the CLI, not the plugin.
|
|
13
13
|
*
|
|
14
14
|
* Usage:
|
|
15
15
|
* node capture-runner.js <source> <event>
|
|
16
16
|
* source: 'claude-code' (today) | future: 'codex', …
|
|
17
|
-
* event: 'session-start' | 'stop' | 'subagent-stop' | 'session-end' | 'post-tool-use'
|
|
17
|
+
* event: 'session-start' | 'stop' | 'subagent-stop' | 'session-end' | 'post-tool-use' | 'pre-compact'
|
|
18
18
|
*
|
|
19
19
|
* Graceful no-ops (exit 0 silently):
|
|
20
20
|
* - GIPITY_CONVERSATION_GUID env var unset (hook fired from a bare
|
|
@@ -304,6 +304,19 @@ async function main() {
|
|
|
304
304
|
case 'session-end':
|
|
305
305
|
await handleSessionEnd(convGuid, hook, source);
|
|
306
306
|
break;
|
|
307
|
+
case 'pre-compact': {
|
|
308
|
+
// Flush the transcript tail BEFORE compaction rewrites it (the
|
|
309
|
+
// watermark replay after a rewrite relies on server dedup, but
|
|
310
|
+
// flushing first keeps ordering clean), then record the boundary.
|
|
311
|
+
await handleStopFamily(convGuid, hook, false);
|
|
312
|
+
const trigger = typeof hook.trigger === 'string' ? hook.trigger : 'auto';
|
|
313
|
+
await postEntries(convGuid, [{
|
|
314
|
+
kind: 'compact',
|
|
315
|
+
trigger,
|
|
316
|
+
source_uuid: `${hook.session_id ?? 'unknown'}-compact-${Date.now()}`,
|
|
317
|
+
}]);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
307
320
|
default:
|
|
308
321
|
return; // unknown event - silent no-op
|
|
309
322
|
}
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,7 @@ import { locationCommand } from './commands/location.js';
|
|
|
52
52
|
import { doctorCommand } from './commands/doctor.js';
|
|
53
53
|
import { updateCommand } from './commands/update.js';
|
|
54
54
|
import { relayCommand } from './commands/relay.js';
|
|
55
|
+
import { setupCommand } from './commands/setup.js';
|
|
55
56
|
import { uninstallCommand } from './commands/uninstall.js';
|
|
56
57
|
import { approvalCommand } from './commands/approval.js';
|
|
57
58
|
import { gmailCommand } from './commands/gmail.js';
|
|
@@ -125,7 +126,7 @@ const program = new Command();
|
|
|
125
126
|
program.enablePositionalOptions();
|
|
126
127
|
// ── Command groups (logical ordering within each) ──────────────────────
|
|
127
128
|
const commonGroup = [skillCommand, projectCommand, addCommand, removeCommand, deployCommand];
|
|
128
|
-
const connectGroup = [claudeCommand, relayCommand];
|
|
129
|
+
const connectGroup = [claudeCommand, setupCommand, relayCommand];
|
|
129
130
|
const projectGroup = [domainCommand, statusCommand, initCommand];
|
|
130
131
|
const filesGroup = [fileCommand, storageCommand, syncCommand, pushCommand, uploadCommand];
|
|
131
132
|
const appBuildingGroup = [testCommand, fnCommand, serviceCommand, secretsCommand, notifyCommand, paymentsCommand, jobCommand, dbCommand, logsCommand, workflowCommand, realtimeCommand, rbacCommand, auditCommand, recordsCommand];
|
|
@@ -180,6 +181,7 @@ program.configureHelp({
|
|
|
180
181
|
lines.push(bold('Quick start:'));
|
|
181
182
|
lines.push(` ${brand('gipity login')} ${dim('- authenticate first if you haven\'t already')}`);
|
|
182
183
|
lines.push(` ${brand('gipity init')} ${dim('- link this dir + write CLAUDE.md/AGENTS.md primers for your AI tool')}`);
|
|
184
|
+
lines.push(` ${brand('gipity setup')} ${dim('- set up this computer as a relay to drive from gipity.ai (no launch)')}`);
|
|
183
185
|
lines.push(` ${brand('gipity claude')} ${dim('- or launch Claude Code with Gipity tools wired in')}`);
|
|
184
186
|
lines.push('');
|
|
185
187
|
lines.push(bold('Usage:'));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive email+code login, shared by every command that may need to log a
|
|
3
|
+
* user in mid-run (`gipity claude`, `gipity setup`). Kept in one place so the
|
|
4
|
+
* prompts, indentation, and token handling never drift between entry points.
|
|
5
|
+
*
|
|
6
|
+
* Distinct from `commands/login.ts` (the standalone `gipity login`, which also
|
|
7
|
+
* supports non-interactive `--email/--code`): this variant is the two-space
|
|
8
|
+
* indented, always-interactive flow used as a sub-step of a larger command, and
|
|
9
|
+
* it returns the fresh `AuthData` instead of exiting.
|
|
10
|
+
*/
|
|
11
|
+
import { publicPost } from './api.js';
|
|
12
|
+
import { getAuth, saveAuth } from './auth.js';
|
|
13
|
+
import { prompt, decodeJwtExp } from './utils.js';
|
|
14
|
+
import { success, error as clrError } from './colors.js';
|
|
15
|
+
/** Prompt for email + 6-digit code, persist the tokens, and return the new
|
|
16
|
+
* auth. Exits the process on empty input or a bad token (the caller can't
|
|
17
|
+
* proceed without a session). */
|
|
18
|
+
export async function interactiveLogin() {
|
|
19
|
+
const email = await prompt(' Email: ');
|
|
20
|
+
if (!email) {
|
|
21
|
+
console.error(`\n ${clrError('Email required.')}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
await publicPost('/auth/login', { email });
|
|
25
|
+
console.log(' Check your email for a 6-digit code.\n');
|
|
26
|
+
const code = await prompt(' Code: ');
|
|
27
|
+
if (!code) {
|
|
28
|
+
console.error(`\n ${clrError('Code required.')}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const res = await publicPost('/auth/verify', { email, code });
|
|
32
|
+
const exp = decodeJwtExp(res.accessToken);
|
|
33
|
+
if (!exp) {
|
|
34
|
+
console.error(`\n ${clrError('Invalid token received.')}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const expiresAt = new Date(exp * 1000).toISOString();
|
|
38
|
+
saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
|
|
39
|
+
console.log(` ${success(`Logged in (${email}).`)}`);
|
|
40
|
+
return getAuth();
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=login-flow.js.map
|