gipity 1.1.1 → 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 +0 -5
- package/dist/agents/codex.js +0 -4
- package/dist/agents/grok.js +0 -4
- package/dist/api.js +7 -4
- package/dist/catalog.js +46 -0
- package/dist/commands/add.js +9 -22
- package/dist/commands/build.js +20 -42
- package/dist/commands/db.js +10 -0
- package/dist/commands/deploy.js +7 -0
- package/dist/commands/fn.js +24 -1
- package/dist/commands/logs.js +21 -2
- package/dist/commands/page-eval.js +7 -3
- package/dist/commands/relay.js +1 -1
- package/dist/commands/status.js +35 -7
- package/dist/commands/test.js +7 -0
- package/dist/flag-aliases.js +1 -0
- package/dist/index.js +109 -77
- package/dist/prefs.js +8 -16
- package/dist/relay/onboarding.js +7 -7
- package/dist/relay/setup.js +8 -8
- package/dist/relay/state.js +1 -1
- package/package.json +1 -1
package/dist/commands/status.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
3
3
|
import { join, resolve } from 'path';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { getAuth, sessionExpired } from '../auth.js';
|
|
6
|
+
import { get, usingEnvToken, ApiError } from '../api.js';
|
|
6
7
|
import { getConfig, liveUrl } from '../config.js';
|
|
7
8
|
import { brand, success, warning, muted, error as clrError } from '../colors.js';
|
|
8
9
|
import { GIPITY_PLUGIN_ID, GIPITY_MARKETPLACE_NAME, setupClaudeHooks, ensureGipityPlugin, ensureGipityPluginInstalled, userScopeInstallState } from '../setup.js';
|
|
@@ -38,6 +39,17 @@ function checkGipityPlugin() {
|
|
|
38
39
|
const stale = missing.length === 1 && missing[0] === 'install' && install.exists;
|
|
39
40
|
return { missing, ok: missing.length === 0, stale };
|
|
40
41
|
}
|
|
42
|
+
async function probeAuth(loggedIn) {
|
|
43
|
+
if (!loggedIn && !usingEnvToken())
|
|
44
|
+
return 'none';
|
|
45
|
+
if (!usingEnvToken() && sessionExpired())
|
|
46
|
+
return 'expired';
|
|
47
|
+
// Cap the probe well below the API layer's 60s request timeout - status is
|
|
48
|
+
// a diagnostic command and must answer fast even when the network is dark.
|
|
49
|
+
const timeout = new Promise(res => setTimeout(() => res('unreachable'), 5000).unref?.());
|
|
50
|
+
const call = get('/users/me').then(() => 'ok', (err) => (err instanceof ApiError && err.statusCode === 401 ? 'rejected' : 'unreachable'));
|
|
51
|
+
return Promise.race([call, timeout]);
|
|
52
|
+
}
|
|
41
53
|
// `whoami` is the name agents reach for first when they want the signed-in
|
|
42
54
|
// identity (it's the unix spelling), and this is the command that prints it.
|
|
43
55
|
// Aliasing costs one line and turns a guess into a hit.
|
|
@@ -58,6 +70,7 @@ export const statusCommand = new Command('status')
|
|
|
58
70
|
// now go out. Skipped entirely (existsSync short-circuit) when the queue
|
|
59
71
|
// is empty, which is the common case, so this stays a no-op most runs.
|
|
60
72
|
const queueDelivered = (auth && !sessionExpired()) ? await flushBugQueue().catch(() => 0) : 0;
|
|
73
|
+
const probe = await probeAuth(!!auth);
|
|
61
74
|
if (opts.json) {
|
|
62
75
|
console.log(JSON.stringify({
|
|
63
76
|
project: config ? {
|
|
@@ -69,9 +82,13 @@ export const statusCommand = new Command('status')
|
|
|
69
82
|
} : null,
|
|
70
83
|
// `valid` reflects the refresh token (the real session) - access
|
|
71
84
|
// tokens auto-renew, so their expiry must not read as "invalid".
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
// `probe` is what one live call just proved: 'rejected' means every
|
|
86
|
+
// authenticated command will fail even though `valid` reads true.
|
|
87
|
+
auth: (auth || usingEnvToken()) ? {
|
|
88
|
+
email: auth?.email,
|
|
89
|
+
source: usingEnvToken() ? 'agent-token' : 'session',
|
|
90
|
+
valid: usingEnvToken() ? probe !== 'rejected' : !sessionExpired(),
|
|
91
|
+
probe,
|
|
75
92
|
} : null,
|
|
76
93
|
plugin: hookCheck,
|
|
77
94
|
}, null, 2));
|
|
@@ -88,14 +105,25 @@ export const statusCommand = new Command('status')
|
|
|
88
105
|
if (config.agentGuid)
|
|
89
106
|
console.log(`${muted('Agent:')} ${config.agentGuid}`);
|
|
90
107
|
}
|
|
91
|
-
if (
|
|
108
|
+
if (usingEnvToken()) {
|
|
109
|
+
console.log(`${muted('Auth:')} ${probe === 'rejected'
|
|
110
|
+
? warning('agent API token (GIPITY_TOKEN) rejected by the server — mint a new one: gipity skill read agent-deploy')
|
|
111
|
+
: success('agent API token (GIPITY_TOKEN)')}${probe === 'unreachable' ? ` ${muted('(unverified — API unreachable)')}` : ''}`);
|
|
112
|
+
}
|
|
113
|
+
else if (!auth) {
|
|
92
114
|
console.log(`${muted('Auth:')} ${warning('not logged in. Run: gipity login')}`);
|
|
93
115
|
}
|
|
94
|
-
else if (
|
|
95
|
-
console.log(`${muted('Auth:')} ${warning(`session expired for ${auth.email}. Run: gipity login`)}`);
|
|
116
|
+
else if (probe === 'expired') {
|
|
117
|
+
console.log(`${muted('Auth:')} ${warning(`session expired for ${auth.email}. Run: gipity login (headless/CI: set GIPITY_TOKEN — gipity skill read agent-deploy)`)}`);
|
|
118
|
+
}
|
|
119
|
+
else if (probe === 'rejected') {
|
|
120
|
+
// Locally fresh but the server says no (refresh token rotated away or
|
|
121
|
+
// revoked). Without the live probe this printed a green identity while
|
|
122
|
+
// every authenticated command failed.
|
|
123
|
+
console.log(`${muted('Auth:')} ${warning(`session for ${auth.email} was rejected by the server. Run: gipity login (headless/CI: set GIPITY_TOKEN — gipity skill read agent-deploy)`)}`);
|
|
96
124
|
}
|
|
97
125
|
else {
|
|
98
|
-
console.log(`${muted('Auth:')} ${success(auth.email)}`);
|
|
126
|
+
console.log(`${muted('Auth:')} ${success(auth.email)}${probe === 'unreachable' ? ` ${muted('(unverified — API unreachable)')}` : ''}`);
|
|
99
127
|
}
|
|
100
128
|
if (queueDelivered > 0) {
|
|
101
129
|
console.log(`${muted('Bug queue:')} ${success(`delivered ${queueDelivered} queued bug report${queueDelivered === 1 ? '' : 's'}`)}`);
|
package/dist/commands/test.js
CHANGED
|
@@ -206,6 +206,13 @@ export const testCommand = new Command('test')
|
|
|
206
206
|
console.log(clrError(`Run failed: ${data.errorMessage}`));
|
|
207
207
|
console.log('');
|
|
208
208
|
}
|
|
209
|
+
// What the isolated test DB reset did (tables truncated, test.preserve
|
|
210
|
+
// list, seeds re-applied) - so "expected N rows, got 0" reads as "the
|
|
211
|
+
// reset truncated that table", not as a mystery.
|
|
212
|
+
if (data.dbSummary) {
|
|
213
|
+
console.log(muted(`Test db: ${data.dbSummary}`));
|
|
214
|
+
console.log('');
|
|
215
|
+
}
|
|
209
216
|
// Failures recap. Results stream incrementally above, but agents usually
|
|
210
217
|
// read this output through a `tail` window, so anything printed early is
|
|
211
218
|
// lost - restate every failure (name + assertion message) right before
|
package/dist/flag-aliases.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -6615,7 +6615,8 @@ __export(api_exports, {
|
|
|
6615
6615
|
put: () => put,
|
|
6616
6616
|
putTimeoutMs: () => putTimeoutMs,
|
|
6617
6617
|
putToPresignedUrl: () => putToPresignedUrl,
|
|
6618
|
-
sendMessage: () => sendMessage
|
|
6618
|
+
sendMessage: () => sendMessage,
|
|
6619
|
+
usingEnvToken: () => usingEnvToken
|
|
6619
6620
|
});
|
|
6620
6621
|
import { Readable } from "stream";
|
|
6621
6622
|
async function fetchWithTimeout(url, init, timeoutMs, label2) {
|
|
@@ -6644,7 +6645,7 @@ async function shouldRetryAfter401(status, retried) {
|
|
|
6644
6645
|
return forceRefreshAccessToken();
|
|
6645
6646
|
}
|
|
6646
6647
|
function with401Hint(status, message) {
|
|
6647
|
-
return status === 401 && !usingEnvToken() ? `${message} \u2014 run: gipity login` : message;
|
|
6648
|
+
return status === 401 && !usingEnvToken() ? `${message} \u2014 run: gipity login (headless/CI: set GIPITY_TOKEN instead \u2014 gipity skill read agent-deploy)` : message;
|
|
6648
6649
|
}
|
|
6649
6650
|
async function getHeaders() {
|
|
6650
6651
|
return {
|
|
@@ -34987,6 +34988,7 @@ Working with an existing Gipity project:
|
|
|
34987
34988
|
|
|
34988
34989
|
// src/commands/status.ts
|
|
34989
34990
|
init_auth();
|
|
34991
|
+
init_api();
|
|
34990
34992
|
init_config();
|
|
34991
34993
|
init_colors();
|
|
34992
34994
|
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
@@ -35009,6 +35011,16 @@ function checkGipityPlugin() {
|
|
|
35009
35011
|
const stale = missing.length === 1 && missing[0] === "install" && install.exists;
|
|
35010
35012
|
return { missing, ok: missing.length === 0, stale };
|
|
35011
35013
|
}
|
|
35014
|
+
async function probeAuth(loggedIn) {
|
|
35015
|
+
if (!loggedIn && !usingEnvToken()) return "none";
|
|
35016
|
+
if (!usingEnvToken() && sessionExpired()) return "expired";
|
|
35017
|
+
const timeout = new Promise((res) => setTimeout(() => res("unreachable"), 5e3).unref?.());
|
|
35018
|
+
const call = get("/users/me").then(
|
|
35019
|
+
() => "ok",
|
|
35020
|
+
(err) => err instanceof ApiError && err.statusCode === 401 ? "rejected" : "unreachable"
|
|
35021
|
+
);
|
|
35022
|
+
return Promise.race([call, timeout]);
|
|
35023
|
+
}
|
|
35012
35024
|
var statusCommand = new Command("status").alias("whoami").description("Show project and login status").option("--json", "Output as JSON").option("--repair-hooks", "Re-enable the Gipity Claude Code plugin (hooks) if missing or disabled").action(async (opts) => {
|
|
35013
35025
|
const config = getConfig();
|
|
35014
35026
|
const auth = getAuth();
|
|
@@ -35016,6 +35028,7 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35016
35028
|
void cwd;
|
|
35017
35029
|
const hookCheck = config ? checkGipityPlugin() : null;
|
|
35018
35030
|
const queueDelivered = auth && !sessionExpired() ? await flushBugQueue().catch(() => 0) : 0;
|
|
35031
|
+
const probe = await probeAuth(!!auth);
|
|
35019
35032
|
if (opts.json) {
|
|
35020
35033
|
console.log(JSON.stringify({
|
|
35021
35034
|
project: config ? {
|
|
@@ -35027,9 +35040,13 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35027
35040
|
} : null,
|
|
35028
35041
|
// `valid` reflects the refresh token (the real session) - access
|
|
35029
35042
|
// tokens auto-renew, so their expiry must not read as "invalid".
|
|
35030
|
-
|
|
35031
|
-
|
|
35032
|
-
|
|
35043
|
+
// `probe` is what one live call just proved: 'rejected' means every
|
|
35044
|
+
// authenticated command will fail even though `valid` reads true.
|
|
35045
|
+
auth: auth || usingEnvToken() ? {
|
|
35046
|
+
email: auth?.email,
|
|
35047
|
+
source: usingEnvToken() ? "agent-token" : "session",
|
|
35048
|
+
valid: usingEnvToken() ? probe !== "rejected" : !sessionExpired(),
|
|
35049
|
+
probe
|
|
35033
35050
|
} : null,
|
|
35034
35051
|
plugin: hookCheck
|
|
35035
35052
|
}, null, 2));
|
|
@@ -35044,12 +35061,16 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35044
35061
|
console.log(`${muted("API:")} ${config.apiBase}`);
|
|
35045
35062
|
if (config.agentGuid) console.log(`${muted("Agent:")} ${config.agentGuid}`);
|
|
35046
35063
|
}
|
|
35047
|
-
if (
|
|
35064
|
+
if (usingEnvToken()) {
|
|
35065
|
+
console.log(`${muted("Auth:")} ${probe === "rejected" ? warning("agent API token (GIPITY_TOKEN) rejected by the server \u2014 mint a new one: gipity skill read agent-deploy") : success("agent API token (GIPITY_TOKEN)")}${probe === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
|
|
35066
|
+
} else if (!auth) {
|
|
35048
35067
|
console.log(`${muted("Auth:")} ${warning("not logged in. Run: gipity login")}`);
|
|
35049
|
-
} else if (
|
|
35050
|
-
console.log(`${muted("Auth:")} ${warning(`session expired for ${auth.email}. Run: gipity login`)}`);
|
|
35068
|
+
} else if (probe === "expired") {
|
|
35069
|
+
console.log(`${muted("Auth:")} ${warning(`session expired for ${auth.email}. Run: gipity login (headless/CI: set GIPITY_TOKEN \u2014 gipity skill read agent-deploy)`)}`);
|
|
35070
|
+
} else if (probe === "rejected") {
|
|
35071
|
+
console.log(`${muted("Auth:")} ${warning(`session for ${auth.email} was rejected by the server. Run: gipity login (headless/CI: set GIPITY_TOKEN \u2014 gipity skill read agent-deploy)`)}`);
|
|
35051
35072
|
} else {
|
|
35052
|
-
console.log(`${muted("Auth:")} ${success(auth.email)}`);
|
|
35073
|
+
console.log(`${muted("Auth:")} ${success(auth.email)}${probe === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
|
|
35053
35074
|
}
|
|
35054
35075
|
if (queueDelivered > 0) {
|
|
35055
35076
|
console.log(`${muted("Bug queue:")} ${success(`delivered ${queueDelivered} queued bug report${queueDelivered === 1 ? "" : "s"}`)}`);
|
|
@@ -35466,7 +35487,7 @@ function slowRenderMessage(fps, o) {
|
|
|
35466
35487
|
const frames = Math.max(1, Math.round(fps * (o.waitMs / 1e3)));
|
|
35467
35488
|
return `${warning("\u26A0 Slow render:")} page painted at ${fps} fps, so the app's vision pipeline ran on roughly ${bold(`${frames} frame${frames === 1 ? "" : "s"}`)} during the ${Math.round(o.waitMs / 1e3)}s before this eval (it infers once per painted frame). ${bold("That is enough:")} --camera loops your still image, so every one of those frames is the SAME pixels and the model returns the SAME answer on each \u2014 re-running with a bigger --wait/--timeout cannot change the result. ${bold("Do not escalate the wait.")} If a detection landed, it is real. If nothing was detected, the suspect is the ${bold("frame")} (a model needs the whole subject in shot \u2014 a tight crop, an odd angle or a busy background reads as nothing) or the ${bold("app")} (frames never reach the model) \u2014 never the frame rate. Settle it in ONE run: ${CAMERA_FRAME_CHECK}`;
|
|
35468
35489
|
}
|
|
35469
|
-
return `${warning("\u26A0 Slow render:")} page painted at ${fps} fps. Waiting on real time (setTimeout) advances animation/physics time far slower than it looks \u2014 assertions after a wall-clock wait can report a false negative. Step the app's own loop deterministically instead (3D templates: ${bold("core.advance(seconds)")}).`;
|
|
35490
|
+
return `${warning("\u26A0 Slow render:")} page painted at ${fps} fps. Waiting on real time (setTimeout) advances animation/physics time far slower than it looks \u2014 assertions after a wall-clock wait can report a false negative. Step the app's own loop deterministically instead (3D templates: ${bold("core.advance(seconds)")}; 2D/Phaser template: ${bold("(await import('./js/config.js')).advance(seconds)")}).`;
|
|
35470
35491
|
}
|
|
35471
35492
|
async function pollEvalResult(evalJobId, expectedWorkMs) {
|
|
35472
35493
|
const deadline = Date.now() + expectedWorkMs + 6e4;
|
|
@@ -35670,7 +35691,7 @@ ${hint}`);
|
|
|
35670
35691
|
}));
|
|
35671
35692
|
return;
|
|
35672
35693
|
}
|
|
35673
|
-
console.
|
|
35694
|
+
console.error(`${brand("Eval")} ${bold(d.url || url)}`);
|
|
35674
35695
|
if (d.navigationIncomplete) {
|
|
35675
35696
|
console.log(`${warning("\u26A0 Navigation incomplete:")} ${d.note || "page did not reach full load"}`);
|
|
35676
35697
|
}
|
|
@@ -35685,7 +35706,7 @@ ${hint}`);
|
|
|
35685
35706
|
}
|
|
35686
35707
|
if (camera) console.log(`${muted("Camera:")} ${camera.name} ${muted("(played as the webcam feed; getUserMedia resolves)")}`);
|
|
35687
35708
|
if (hosted.length) console.log(`${muted("Fixtures:")} ${hosted.map((h) => h.name).join(", ")}`);
|
|
35688
|
-
console.
|
|
35709
|
+
console.error(opts.file ? `${muted("Script:")} ${opts.file}` : `${muted("Expression:")} ${summarizeExpr(expr)}`);
|
|
35689
35710
|
console.log(`
|
|
35690
35711
|
${result.trim() ? result : muted("(empty result)")}`);
|
|
35691
35712
|
if (noValue) console.log(muted(`
|
|
@@ -36433,6 +36454,10 @@ var deployCommand = new Command("deploy").description("Deploy to dev or prod").a
|
|
|
36433
36454
|
} else {
|
|
36434
36455
|
console.log(success(`\u2713 Deployed to ${target}`) + muted(` (${d.elapsedMs}ms)`));
|
|
36435
36456
|
if (d.url) console.log(`${muted("Live:")} ${brand(d.url)}`);
|
|
36457
|
+
if (d.phases?.some((p) => p.name === "functions")) {
|
|
36458
|
+
console.log(`${muted("Functions:")} POST ${brand(`${config.apiBase}/api/${config.projectGuid}/fn/<name>`)}`);
|
|
36459
|
+
console.log(muted(" (same address on dev and prod; names: gipity fn list; verify the public path: gipity fn call <name> --anon)"));
|
|
36460
|
+
}
|
|
36436
36461
|
await inspectAfter();
|
|
36437
36462
|
}
|
|
36438
36463
|
}));
|
|
@@ -36470,10 +36495,15 @@ dbCommand.command("query <sql>").description("Run SQL").option("--database <name
|
|
|
36470
36495
|
console.log(columns.map((c) => String(row2[c] ?? "NULL")).join(" "));
|
|
36471
36496
|
}
|
|
36472
36497
|
}
|
|
36498
|
+
if (res.data.affectedRows !== void 0) {
|
|
36499
|
+
console.log(`Affected rows: ${res.data.affectedRows}`);
|
|
36500
|
+
}
|
|
36473
36501
|
} else if (res.data.affectedRows !== void 0) {
|
|
36474
36502
|
console.log(`Affected rows: ${res.data.affectedRows}`);
|
|
36475
36503
|
} else if (res.data.results) {
|
|
36476
36504
|
console.log(JSON.stringify(res.data.results, null, 2));
|
|
36505
|
+
} else {
|
|
36506
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
36477
36507
|
}
|
|
36478
36508
|
}
|
|
36479
36509
|
}));
|
|
@@ -38348,10 +38378,6 @@ async function pairDevice(opts = {}) {
|
|
|
38348
38378
|
};
|
|
38349
38379
|
}
|
|
38350
38380
|
if (existing && opts.force) {
|
|
38351
|
-
try {
|
|
38352
|
-
await post(`/remote-devices/${encodeURIComponent(existing.guid)}/revoke`, {});
|
|
38353
|
-
} catch {
|
|
38354
|
-
}
|
|
38355
38381
|
clearDevice();
|
|
38356
38382
|
}
|
|
38357
38383
|
const name = (opts.name?.trim() || friendlyDeviceName()).trim();
|
|
@@ -38443,20 +38469,20 @@ async function runRelaySetup(opts) {
|
|
|
38443
38469
|
console.log(` ${dim("To register this computer again \u2014 for example under a different name \u2014")}`);
|
|
38444
38470
|
console.log(` ${dim("unregister it first, then re-run setup:")}`);
|
|
38445
38471
|
console.log(` ${brand("gipity relay revoke")} ${dim("# unpairs this computer and removes the login service")}`);
|
|
38446
|
-
console.log(` ${brand("gipity
|
|
38472
|
+
console.log(` ${brand("gipity connect")} ${dim("# register it again (asks for a new name)")}`);
|
|
38447
38473
|
console.log("");
|
|
38448
38474
|
}
|
|
38449
38475
|
return true;
|
|
38450
38476
|
}
|
|
38451
38477
|
if (opts.mode === "run-now") {
|
|
38452
38478
|
console.log(` ${bold("Set up this computer as a relay")}`);
|
|
38453
|
-
console.log(` ${dim("A relay runs Claude Code
|
|
38454
|
-
console.log(` ${dim("It uses your Claude or
|
|
38479
|
+
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.")}`);
|
|
38480
|
+
console.log(` ${dim("It uses your Claude, Codex, or Grok subscription \u2014 the cheapest way to pay for tokens.")}`);
|
|
38455
38481
|
} else {
|
|
38456
|
-
console.log(` ${bold("Remote control of
|
|
38457
|
-
console.log(` ${dim("Drive this
|
|
38482
|
+
console.log(` ${bold("Remote control of your coding agent")}`);
|
|
38483
|
+
console.log(` ${dim("Drive your coding agent on this computer from the web (")}${brand("gipity.ai")}${dim(") on any browser (desktop or phone).")}`);
|
|
38458
38484
|
console.log("");
|
|
38459
|
-
console.log(` ${dim("Enable now (takes 2 seconds) or turn on later with")} ${brand("gipity
|
|
38485
|
+
console.log(` ${dim("Enable now (takes 2 seconds) or turn on later with")} ${brand("gipity connect")}`);
|
|
38460
38486
|
}
|
|
38461
38487
|
console.log("");
|
|
38462
38488
|
const promptText = opts.mode === "run-now" ? " Set up remote control on this computer?" : " Enable remote control?";
|
|
@@ -38481,7 +38507,7 @@ async function runRelaySetup(opts) {
|
|
|
38481
38507
|
} catch (err) {
|
|
38482
38508
|
console.error(`
|
|
38483
38509
|
${error(`Could not create device: ${err?.message || err}`)}`);
|
|
38484
|
-
console.error(` ${dim("Skipping for now - we'll offer again next time. Or turn it on with `gipity
|
|
38510
|
+
console.error(` ${dim("Skipping for now - we'll offer again next time. Or turn it on with `gipity connect`.")}`);
|
|
38485
38511
|
return false;
|
|
38486
38512
|
}
|
|
38487
38513
|
const startNow = await confirm(" Start the relay now (and on future `gipity build` runs)?", { default: "yes" });
|
|
@@ -38840,11 +38866,6 @@ var claudeCodeAdapter = {
|
|
|
38840
38866
|
displayName: "Claude Code",
|
|
38841
38867
|
providerName: "Anthropic",
|
|
38842
38868
|
binary: "claude",
|
|
38843
|
-
models: [
|
|
38844
|
-
{ id: "opus", label: "Opus" },
|
|
38845
|
-
{ id: "sonnet", label: "Sonnet" },
|
|
38846
|
-
{ id: "haiku", label: "Haiku" }
|
|
38847
|
-
],
|
|
38848
38869
|
buildInteractiveArgs({ resume, model }) {
|
|
38849
38870
|
const args = [];
|
|
38850
38871
|
if (model) args.push("--model", model);
|
|
@@ -38882,10 +38903,6 @@ var codexAdapter = {
|
|
|
38882
38903
|
displayName: "Codex",
|
|
38883
38904
|
providerName: "OpenAI",
|
|
38884
38905
|
binary: "codex",
|
|
38885
|
-
models: [
|
|
38886
|
-
{ id: "gpt-5.1-codex", label: "GPT-5.1 Codex" },
|
|
38887
|
-
{ id: "gpt-5.1-codex-mini", label: "GPT-5.1 Codex Mini" }
|
|
38888
|
-
],
|
|
38889
38906
|
buildInteractiveArgs({ resume, model }) {
|
|
38890
38907
|
const args = [];
|
|
38891
38908
|
if (model) args.push("--model", model);
|
|
@@ -38932,10 +38949,6 @@ var grokAdapter = {
|
|
|
38932
38949
|
displayName: "Grok",
|
|
38933
38950
|
providerName: "xAI",
|
|
38934
38951
|
binary: "grok",
|
|
38935
|
-
models: [
|
|
38936
|
-
{ id: "grok-4.5", label: "Grok 4.5" },
|
|
38937
|
-
{ id: "grok-code", label: "Grok Code" }
|
|
38938
|
-
],
|
|
38939
38952
|
buildInteractiveArgs({ resume, model }) {
|
|
38940
38953
|
const args = [];
|
|
38941
38954
|
if (model) args.push("--model", model);
|
|
@@ -38998,8 +39011,7 @@ function readPrefs() {
|
|
|
38998
39011
|
try {
|
|
38999
39012
|
const parsed = JSON.parse(readFileSync16(PREFS_PATH, "utf-8"));
|
|
39000
39013
|
return {
|
|
39001
|
-
lastAgent: typeof parsed.lastAgent === "string" ? parsed.lastAgent : void 0
|
|
39002
|
-
lastModel: parsed.lastModel && typeof parsed.lastModel === "object" ? parsed.lastModel : void 0
|
|
39014
|
+
lastAgent: typeof parsed.lastAgent === "string" ? parsed.lastAgent : void 0
|
|
39003
39015
|
};
|
|
39004
39016
|
} catch {
|
|
39005
39017
|
return {};
|
|
@@ -39008,11 +39020,7 @@ function readPrefs() {
|
|
|
39008
39020
|
function writePrefs(update) {
|
|
39009
39021
|
try {
|
|
39010
39022
|
const current = readPrefs();
|
|
39011
|
-
const next = {
|
|
39012
|
-
...current,
|
|
39013
|
-
...update,
|
|
39014
|
-
lastModel: { ...current.lastModel, ...update.lastModel }
|
|
39015
|
-
};
|
|
39023
|
+
const next = { ...current, ...update };
|
|
39016
39024
|
mkdirSync11(join16(homedir11(), ".gipity"), { recursive: true });
|
|
39017
39025
|
writeFileSync11(PREFS_PATH, JSON.stringify(next, null, 2) + "\n");
|
|
39018
39026
|
} catch {
|
|
@@ -39154,7 +39162,7 @@ function suggestProjectName(existingSlugs) {
|
|
|
39154
39162
|
return `project-${Date.now().toString(36).slice(-6)}`;
|
|
39155
39163
|
}
|
|
39156
39164
|
function createLaunchCommand(name, cfg = {}) {
|
|
39157
|
-
const cmd = new Command(name).description(cfg.presetAgent ? "Set up and run Claude Code (legacy alias of `gipity build`)" : "Start building from anywhere - pick a project, pick your coding agent, go").option("--setup-only", "Do the Gipity setup but skip launching the agent").option("--new-project", "Create a fresh Gipity project instead of using cwd or the picker").option("--name <name>", "Name for --new-project (default: project-NNN)").option("--project <slug>", "Open an existing project by slug or id").option("--here", "Use the current directory instead of ~/GipityProjects/<slug>/").option("--quiet", "Suppress the agent's live progress output (headless --new-project/--project runs)").option("--model <model>", "Model for the session (e.g. opus
|
|
39165
|
+
const cmd = new Command(name).description(cfg.presetAgent ? "Set up and run Claude Code (legacy alias of `gipity build`)" : "Start building from anywhere - pick a project, pick your coding agent, go").option("--setup-only", "Do the Gipity setup but skip launching the agent").option("--new-project", "Create a fresh Gipity project instead of using cwd or the picker").option("--name <name>", "Name for --new-project (default: project-NNN)").option("--project <slug>", "Open an existing project by slug or id").option("--here", "Use the current directory instead of ~/GipityProjects/<slug>/").option("--quiet", "Suppress the agent's live progress output (headless --new-project/--project runs)").option("--model <model>", "Model for the session, passed straight to the agent (e.g. opus) - omit it and the agent uses its own default").option("--bypass-approvals", "Skip the agent's interactive tool approvals (headless runs; the relay daemon sets this)").allowUnknownOption(true).allowExcessArguments(true);
|
|
39158
39166
|
if (!cfg.presetAgent) {
|
|
39159
39167
|
cmd.option("--agent <agent>", `Which coding agent to launch: ${AGENT_KEYS.join(", ")} (default: your last-used)`);
|
|
39160
39168
|
}
|
|
@@ -39471,11 +39479,8 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39471
39479
|
}
|
|
39472
39480
|
}
|
|
39473
39481
|
const adapter = getAdapter(agentKey);
|
|
39474
|
-
|
|
39475
|
-
|
|
39476
|
-
if (!nonInteractive && !modelFlagGiven && !cmdCfg.presetAgent) {
|
|
39477
|
-
pickedModel = await pickModel(adapter, prefs.lastModel?.[adapter.key] ?? null);
|
|
39478
|
-
writePrefs({ lastAgent: adapter.key, lastModel: { [adapter.key]: pickedModel } });
|
|
39482
|
+
if (!nonInteractive && !cmdCfg.presetAgent) {
|
|
39483
|
+
writePrefs({ lastAgent: adapter.key });
|
|
39479
39484
|
}
|
|
39480
39485
|
if (adapter.key !== "claude") {
|
|
39481
39486
|
await launchNonClaudeAgent(adapter, {
|
|
@@ -39484,8 +39489,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39484
39489
|
rawArgs,
|
|
39485
39490
|
nonInteractive,
|
|
39486
39491
|
headlessOut,
|
|
39487
|
-
runStart
|
|
39488
|
-
pickedModel
|
|
39492
|
+
runStart
|
|
39489
39493
|
});
|
|
39490
39494
|
return;
|
|
39491
39495
|
}
|
|
@@ -39594,7 +39598,6 @@ Sending to Claude Code: ${userMsg}
|
|
|
39594
39598
|
}
|
|
39595
39599
|
} else {
|
|
39596
39600
|
allArgs = initialPrompt ? [initialPrompt, ...claudeArgs] : claudeArgs;
|
|
39597
|
-
if (pickedModel) allArgs.push("--model", pickedModel);
|
|
39598
39601
|
}
|
|
39599
39602
|
if (nonInteractive && (opts.newProject || opts.project)) {
|
|
39600
39603
|
const hasPermFlag = allArgs.some((a) => a === "--permission-mode" || a.startsWith("--permission-mode=") || a === "--dangerously-skip-permissions");
|
|
@@ -39665,23 +39668,8 @@ async function pickAgent(lastUsed) {
|
|
|
39665
39668
|
console.log("");
|
|
39666
39669
|
return AGENT_ADAPTERS[choice - 1].key;
|
|
39667
39670
|
}
|
|
39668
|
-
async function pickModel(adapter, lastUsed) {
|
|
39669
|
-
console.log(` ${bold("Which model?")}
|
|
39670
|
-
`);
|
|
39671
|
-
const lastIsModel = lastUsed && adapter.models.some((m) => m.id === lastUsed);
|
|
39672
|
-
const defaultIdx = lastIsModel ? adapter.models.findIndex((m) => m.id === lastUsed) + 2 : 1;
|
|
39673
|
-
console.log(` ${bold("1.")} Agent default ${muted(defaultIdx === 1 ? "(recommended)" : "")}`);
|
|
39674
|
-
adapter.models.forEach((m, i) => {
|
|
39675
|
-
const note = m.id === lastUsed ? ` ${muted("(last used)")}` : "";
|
|
39676
|
-
console.log(` ${bold(`${i + 2}.`)} ${m.label}${note}`);
|
|
39677
|
-
});
|
|
39678
|
-
console.log("");
|
|
39679
|
-
const choice = await pickOne("Choose", adapter.models.length + 1, defaultIdx);
|
|
39680
|
-
console.log("");
|
|
39681
|
-
return choice === 1 ? null : adapter.models[choice - 2].id;
|
|
39682
|
-
}
|
|
39683
39671
|
async function launchNonClaudeAgent(adapter, ctx) {
|
|
39684
|
-
const { opts, rawArgs, nonInteractive, headlessOut, runStart
|
|
39672
|
+
const { opts, rawArgs, nonInteractive, headlessOut, runStart } = ctx;
|
|
39685
39673
|
if (!binaryOnPath(adapter.binary)) {
|
|
39686
39674
|
let installed = false;
|
|
39687
39675
|
if (adapter.ensureInstalled) {
|
|
@@ -39698,7 +39686,7 @@ async function launchNonClaudeAgent(adapter, ctx) {
|
|
|
39698
39686
|
const valueFlags = ["--api-base", "--name", "--project", "--agent"];
|
|
39699
39687
|
let message = null;
|
|
39700
39688
|
let resume;
|
|
39701
|
-
let model
|
|
39689
|
+
let model;
|
|
39702
39690
|
const extras = [];
|
|
39703
39691
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
39704
39692
|
const arg = rawArgs[i];
|
|
@@ -39977,18 +39965,23 @@ import os from "os";
|
|
|
39977
39965
|
init_api();
|
|
39978
39966
|
init_config();
|
|
39979
39967
|
init_colors();
|
|
39968
|
+
|
|
39969
|
+
// src/catalog.ts
|
|
39980
39970
|
var STARTERS = [
|
|
39981
39971
|
{ key: "web-vision-cam", hint: "fullscreen camera app with on-device vision (MediaPipe)" },
|
|
39982
39972
|
{ key: "object-spotter", hint: "camera app that boxes, labels, and counts objects (YOLOX on-device)" },
|
|
39983
39973
|
{ key: "2d-game", hint: "2D games with Phaser 3 - platformer, arcade, puzzle" },
|
|
39984
39974
|
{ key: "3d-world", hint: "playable 3D multiplayer rocket-launcher demo" },
|
|
39985
|
-
{ key: "karaoke-captions", hint: "audio + lyrics -> word-synced karaoke captions (GPU job)" }
|
|
39975
|
+
{ key: "karaoke-captions", hint: "audio + lyrics -> word-synced karaoke captions (GPU job)" },
|
|
39976
|
+
{ key: "outreach-agent", hint: "AI-run outreach funnel - import contacts, draft + auto-send staged emails" },
|
|
39977
|
+
{ key: "paid-app", hint: "storefront that charges real money - Stripe checkout, members area, billing" },
|
|
39978
|
+
{ key: "notify-demo", hint: "web-push demo - enable notifications, send a real ping" }
|
|
39986
39979
|
];
|
|
39987
39980
|
var BLANK = [
|
|
39988
39981
|
{ key: "web-simple", hint: "static frontend-only site - pages, dashboards, simple games" },
|
|
39989
39982
|
{ key: "web-fullstack", hint: "backend API + database wiring - frontend, functions, migrations; deploys green" },
|
|
39990
|
-
{ key: "
|
|
39991
|
-
{ key: "
|
|
39983
|
+
{ key: "3d-engine", hint: "3D multiplayer wiring - Three.js + Rapier + Gipity Realtime" },
|
|
39984
|
+
{ key: "api", hint: "pure API backend, no frontend - one example function + test" }
|
|
39992
39985
|
];
|
|
39993
39986
|
var KITS = [
|
|
39994
39987
|
{ key: "realtime", hint: "multiplayer / presence / shared state" },
|
|
@@ -39996,8 +39989,16 @@ var KITS = [
|
|
|
39996
39989
|
{ key: "web-vision-detect", hint: "browser object detection - YOLOX, WebGPU/WASM, custom models" },
|
|
39997
39990
|
{ key: "chatbot", hint: "drop-in chatbot - persona, guardrails, streaming responses" },
|
|
39998
39991
|
{ key: "audio-align", hint: "audio + lyrics -> word-level timing JSON (GPU job)" },
|
|
39999
|
-
{ key: "i18n", hint: "multi-language web apps - language picker, RTL, translations" }
|
|
39992
|
+
{ key: "i18n", hint: "multi-language web apps - language picker, RTL, translations" },
|
|
39993
|
+
{ key: "records", hint: "registry-driven data plane - generic CRUD, validation, search, audit spine" },
|
|
39994
|
+
{ key: "views", hint: "registry-driven UI over records - table, forms, kanban" },
|
|
39995
|
+
{ key: "agent-api", hint: "named API keys for agent/script writes through the records kit" },
|
|
39996
|
+
{ key: "contacts", hint: "multi-source contact layer - LinkedIn/Gmail import, dedupe with provenance" },
|
|
39997
|
+
{ key: "stripe", hint: "charge your users - Stripe checkout, subscriptions, brokered webhooks" },
|
|
39998
|
+
{ key: "notify", hint: "web push notifications - platform-owned keys, works on iOS home screen" }
|
|
40000
39999
|
];
|
|
40000
|
+
|
|
40001
|
+
// src/commands/add.ts
|
|
40001
40002
|
function catalogText() {
|
|
40002
40003
|
const width = Math.max(...[...STARTERS, ...BLANK, ...KITS].map((e) => e.key.length));
|
|
40003
40004
|
const row2 = (e) => ` ${e.key.padEnd(width)} ${muted(e.hint)}`;
|
|
@@ -40451,7 +40452,19 @@ init_api();
|
|
|
40451
40452
|
init_config();
|
|
40452
40453
|
init_colors();
|
|
40453
40454
|
var logsCommand = new Command("logs").description("View logs");
|
|
40454
|
-
logsCommand.command("fn <name>").description("Show function logs").option("--limit <n>", "Max entries", "20").option("--json", "Output as JSON
|
|
40455
|
+
logsCommand.command("fn <name>").description("Show function logs").option("--limit <n>", "Max entries", "20").option("--json", "Output as JSON (top-level ARRAY of invocations, newest first)").addHelpText("after", `
|
|
40456
|
+
Each line: time, status (ok / error / limit_exceeded), duration, trigger, and
|
|
40457
|
+
svc:N when the invocation made N platform service calls (notify, email, LLM,
|
|
40458
|
+
image, ...). Captured console output is indented beneath its invocation.
|
|
40459
|
+
|
|
40460
|
+
--json emits a top-level ARRAY (not an object) of invocation rows:
|
|
40461
|
+
[{ id, status, duration_ms, trigger_type, error_message,
|
|
40462
|
+
limits_consumed: { max_queries, max_service_calls, ... }, logs: [...], created_at }]
|
|
40463
|
+
|
|
40464
|
+
To confirm an individual service call succeeded (and see provider, latency,
|
|
40465
|
+
and its own error message), use the per-call log instead:
|
|
40466
|
+
$ gipity logs app --type services
|
|
40467
|
+
`).action((name, opts) => run("Logs", async () => {
|
|
40455
40468
|
const config = requireConfig();
|
|
40456
40469
|
const limit = parseInt(opts.limit, 10) || 20;
|
|
40457
40470
|
const res = await get(
|
|
@@ -40472,8 +40485,10 @@ logsCommand.command("fn <name>").description("Show function logs").option("--lim
|
|
|
40472
40485
|
const statusColor = log3.status === "ok" ? success : log3.status === "error" ? error : warning;
|
|
40473
40486
|
const status = statusColor(log3.status.padEnd(8));
|
|
40474
40487
|
const trigger = muted(log3.trigger_type.padEnd(8));
|
|
40488
|
+
const svcCalls = log3.limits_consumed?.max_service_calls;
|
|
40489
|
+
const svc = svcCalls ? ` ${muted(`svc:${svcCalls}`)}` : "";
|
|
40475
40490
|
const err = log3.error_message ? ` ${error(`"${log3.error_message}"`)}` : "";
|
|
40476
|
-
console.log(`${muted(time)} ${status} ${dur} ${trigger}${err}`);
|
|
40491
|
+
console.log(`${muted(time)} ${status} ${dur} ${trigger}${svc}${err}`);
|
|
40477
40492
|
for (const line of log3.logs ?? []) {
|
|
40478
40493
|
const lvlColor = line.level === "error" ? error : line.level === "warn" ? warning : muted;
|
|
40479
40494
|
const tag = line.level === "log" ? "" : `${line.level}: `;
|
|
@@ -41026,6 +41041,7 @@ recordsCommand.command("delete <table> <id>").description("Delete a record").act
|
|
|
41026
41041
|
|
|
41027
41042
|
// src/commands/fn.ts
|
|
41028
41043
|
init_api();
|
|
41044
|
+
init_auth();
|
|
41029
41045
|
init_config();
|
|
41030
41046
|
init_colors();
|
|
41031
41047
|
init_utils();
|
|
@@ -41038,6 +41054,9 @@ fnCommand.command("list").description("List functions").option("--json", "Output
|
|
|
41038
41054
|
return f.description ? `${line}
|
|
41039
41055
|
${muted(f.description)}` : line;
|
|
41040
41056
|
});
|
|
41057
|
+
if (!opts.json && res.data.length > 0) {
|
|
41058
|
+
console.log(muted(`Endpoint: POST ${config.apiBase}/api/${config.projectGuid}/fn/<name> (same on dev and prod; public path: gipity fn call <name> --anon)`));
|
|
41059
|
+
}
|
|
41041
41060
|
}));
|
|
41042
41061
|
fnCommand.command("logs <name>").description("Show recent logs").option("--limit <n>", "Max entries", "20").option("--json", "Output as JSON").action((name, opts) => run("Logs", async () => {
|
|
41043
41062
|
const config = requireConfig();
|
|
@@ -41047,8 +41066,10 @@ fnCommand.command("logs <name>").description("Show recent logs").option("--limit
|
|
|
41047
41066
|
printList(res.data, opts, "No execution logs.", (log3) => {
|
|
41048
41067
|
const dur = log3.duration_ms != null ? `${log3.duration_ms}ms` : "?";
|
|
41049
41068
|
const ts2 = new Date(log3.created_at).toLocaleString();
|
|
41050
|
-
const statusColor = log3.status === "
|
|
41069
|
+
const statusColor = log3.status === "ok" ? success : log3.status === "error" ? error : muted;
|
|
41051
41070
|
let line = `${statusColor(log3.status)} ${dur} ${muted(log3.trigger_type || "http")} ${muted(ts2)}`;
|
|
41071
|
+
const svcCalls = log3.limits_consumed?.max_service_calls;
|
|
41072
|
+
if (svcCalls) line += ` ${muted(`svc:${svcCalls}`)}`;
|
|
41052
41073
|
if (log3.error_message) line += `
|
|
41053
41074
|
${error(`error: ${log3.error_message}`)}`;
|
|
41054
41075
|
for (const entry of log3.logs ?? []) {
|
|
@@ -41078,6 +41099,12 @@ fnCommand.command("call <name> [body]").description("Call a function").option("-
|
|
|
41078
41099
|
const raw = bodyArg || opts.data || "{}";
|
|
41079
41100
|
const body = JSON.parse(raw);
|
|
41080
41101
|
const path5 = `/api/${config.projectGuid}/fn/${encodeURIComponent(name)}`;
|
|
41102
|
+
if (opts.anon) {
|
|
41103
|
+
console.error(muted("Auth: anonymous visitor (the public path a signed-out user hits)"));
|
|
41104
|
+
} else {
|
|
41105
|
+
const who = getAuth()?.email;
|
|
41106
|
+
console.error(muted(`Auth: calling as ${who ?? "your signed-in account"} (the owner persona; use --anon for the public visitor path)`));
|
|
41107
|
+
}
|
|
41081
41108
|
const res = opts.anon ? await callAnon(config.projectGuid, name, body) : await post(path5, body);
|
|
41082
41109
|
if (opts.field) {
|
|
41083
41110
|
emitField(res.data, opts.field);
|
|
@@ -42492,6 +42519,10 @@ var testCommand = new Command("test").description("Run tests").argument("[path]"
|
|
|
42492
42519
|
console.log(error(`Run failed: ${data.errorMessage}`));
|
|
42493
42520
|
console.log("");
|
|
42494
42521
|
}
|
|
42522
|
+
if (data.dbSummary) {
|
|
42523
|
+
console.log(muted(`Test db: ${data.dbSummary}`));
|
|
42524
|
+
console.log("");
|
|
42525
|
+
}
|
|
42495
42526
|
if (data.failed > 0) {
|
|
42496
42527
|
console.log(error("Failures:"));
|
|
42497
42528
|
for (const r of data.results.filter((r7) => r7.status === "failed")) {
|
|
@@ -45575,7 +45606,7 @@ function registerInstallCommands(relayCommand2) {
|
|
|
45575
45606
|
|
|
45576
45607
|
// src/commands/relay.ts
|
|
45577
45608
|
var relayCommand = new Command("relay").description("Pair with the web CLI");
|
|
45578
|
-
relayCommand.command("setup").description("Pair this machine and start the relay - non-interactive (for installers/GUIs)").option("--name <name>", "Device name shown in the web CLI (default: this machine's hostname)").option("--no-start", "Pair only; do not start the relay daemon now").option("--no-autostart", "Skip the OS login service (use when a supervising app owns the daemon)").option("--force", "Re-pair even if already paired (
|
|
45609
|
+
relayCommand.command("setup").description("Pair this machine and start the relay - non-interactive (for installers/GUIs)").option("--name <name>", "Device name shown in the web CLI (default: this machine's hostname)").option("--no-start", "Pair only; do not start the relay daemon now").option("--no-autostart", "Skip the OS login service (use when a supervising app owns the daemon)").option("--force", "Re-pair even if already paired (re-registers this machine, rotating its token)").option("--json", "Machine-readable output").action(async (opts) => {
|
|
45579
45610
|
const fail = (code, message) => {
|
|
45580
45611
|
if (opts.json) {
|
|
45581
45612
|
console.log(JSON.stringify({ ok: false, code, error: message }));
|
|
@@ -46526,6 +46557,7 @@ var FLAG_ALIASES = {
|
|
|
46526
46557
|
"--src": "--source-dir",
|
|
46527
46558
|
"--srcdir": "--source-dir",
|
|
46528
46559
|
"--parallel": "--concurrency",
|
|
46560
|
+
"--no-sync-output": "--discard-output",
|
|
46529
46561
|
"--max": "--limit",
|
|
46530
46562
|
"--from": "--since",
|
|
46531
46563
|
"--after": "--since",
|
package/dist/prefs.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Machine-level user preferences (~/.gipity/prefs.json) - the `gipity build`
|
|
3
|
-
* picker's memory. Deliberately NOT in a project's .gipity.json:
|
|
4
|
-
* storage would re-ask the agent
|
|
5
|
-
* defeats "hit enter twice".
|
|
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
6
|
*
|
|
7
7
|
* Shape:
|
|
8
|
-
* {
|
|
9
|
-
* "lastAgent": "claude",
|
|
10
|
-
* "lastModel": { "claude": "opus", "codex": null } // per-agent so a
|
|
11
|
-
* } // Codex session never
|
|
12
|
-
* // clobbers the Claude pick
|
|
8
|
+
* { "lastAgent": "claude" }
|
|
13
9
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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.
|
|
16
13
|
*/
|
|
17
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
18
15
|
import { homedir } from 'os';
|
|
@@ -25,7 +22,6 @@ export function readPrefs() {
|
|
|
25
22
|
const parsed = JSON.parse(readFileSync(PREFS_PATH, 'utf-8'));
|
|
26
23
|
return {
|
|
27
24
|
lastAgent: typeof parsed.lastAgent === 'string' ? parsed.lastAgent : undefined,
|
|
28
|
-
lastModel: parsed.lastModel && typeof parsed.lastModel === 'object' ? parsed.lastModel : undefined,
|
|
29
25
|
};
|
|
30
26
|
}
|
|
31
27
|
catch {
|
|
@@ -35,11 +31,7 @@ export function readPrefs() {
|
|
|
35
31
|
export function writePrefs(update) {
|
|
36
32
|
try {
|
|
37
33
|
const current = readPrefs();
|
|
38
|
-
const next = {
|
|
39
|
-
...current,
|
|
40
|
-
...update,
|
|
41
|
-
lastModel: { ...current.lastModel, ...update.lastModel },
|
|
42
|
-
};
|
|
34
|
+
const next = { ...current, ...update };
|
|
43
35
|
mkdirSync(join(homedir(), '.gipity'), { recursive: true });
|
|
44
36
|
writeFileSync(PREFS_PATH, JSON.stringify(next, null, 2) + '\n');
|
|
45
37
|
}
|