gipity 1.0.395 → 1.0.397
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/auth.js +110 -46
- package/dist/commands/claude.js +8 -4
- package/dist/commands/sync.js +11 -4
- package/dist/knowledge.js +4 -3
- package/dist/sync.js +10 -4
- package/package.json +2 -2
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync, chmodSync } from 'fs';
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync, chmodSync, openSync, closeSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { decodeJwtExp } from './utils.js';
|
|
@@ -8,6 +8,7 @@ import { decodeJwtExp } from './utils.js';
|
|
|
8
8
|
// so the `claude` subprocess and git/npm still use the real home.
|
|
9
9
|
const AUTH_DIR = process.env.GIPITY_DIR || join(homedir(), '.gipity');
|
|
10
10
|
const AUTH_FILE = join(AUTH_DIR, 'auth.json');
|
|
11
|
+
const AUTH_LOCK_FILE = join(AUTH_DIR, 'auth.lock');
|
|
11
12
|
let cached = null;
|
|
12
13
|
export function getAuth() {
|
|
13
14
|
if (cached)
|
|
@@ -70,16 +71,65 @@ export function sessionExpired() {
|
|
|
70
71
|
return Date.now() > exp * 1000;
|
|
71
72
|
}
|
|
72
73
|
const delay = (ms) => new Promise(r => setTimeout(r, ms));
|
|
74
|
+
// ─── Cross-process refresh lock ────────────────────────────────
|
|
75
|
+
// Serializes token refreshes across every `gipity` process that shares this
|
|
76
|
+
// ~/.gipity/auth.json (foreground commands, the file-sync hook, the relay
|
|
77
|
+
// daemon). Without it, siblings race to refresh the SINGLE-USE refresh token:
|
|
78
|
+
// the first wins and rotates it, the rest submit the now-dead token and 401.
|
|
79
|
+
// Retry-and-re-read alone left a window (a sibling could 401 before the winner
|
|
80
|
+
// finished writing the file). Holding the lock closes that window — siblings
|
|
81
|
+
// wait, then re-read and ADOPT the freshly-rotated token instead of refreshing.
|
|
82
|
+
const LOCK_WAIT_MS = 10_000; // refresh is one quick round-trip; cap the wait
|
|
83
|
+
const LOCK_POLL_MS = 100;
|
|
84
|
+
/** Acquire the global auth-refresh lock. Returns a release fn, or null if we
|
|
85
|
+
* couldn't get it within the wait window (caller then proceeds best-effort —
|
|
86
|
+
* refresh must never block a command indefinitely). Mirrors sync.ts's advisory
|
|
87
|
+
* lock: O_EXCL create, PID payload, stale-holder reclaim. Exported for tests. */
|
|
88
|
+
export async function acquireRefreshLock() {
|
|
89
|
+
mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });
|
|
90
|
+
const start = Date.now();
|
|
91
|
+
while (true) {
|
|
92
|
+
try {
|
|
93
|
+
const fd = openSync(AUTH_LOCK_FILE, 'wx'); // fails if the file exists
|
|
94
|
+
writeFileSync(fd, String(process.pid));
|
|
95
|
+
closeSync(fd);
|
|
96
|
+
return () => { try {
|
|
97
|
+
unlinkSync(AUTH_LOCK_FILE);
|
|
98
|
+
}
|
|
99
|
+
catch { /* already gone */ } };
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Lock held (or a transient create error). Reclaim it if the holder PID is dead.
|
|
103
|
+
try {
|
|
104
|
+
const pid = parseInt(readFileSync(AUTH_LOCK_FILE, 'utf-8').trim(), 10);
|
|
105
|
+
if (pid && !isNaN(pid)) {
|
|
106
|
+
try {
|
|
107
|
+
process.kill(pid, 0);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
try {
|
|
111
|
+
unlinkSync(AUTH_LOCK_FILE);
|
|
112
|
+
}
|
|
113
|
+
catch { /* race */ }
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch { /* unreadable - retry */ }
|
|
119
|
+
if (Date.now() - start > LOCK_WAIT_MS)
|
|
120
|
+
return null; // give up waiting, proceed best-effort
|
|
121
|
+
await delay(LOCK_POLL_MS);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
73
125
|
/** Renew the access token (5-min buffer) before an authenticated call, surviving
|
|
74
126
|
* the case that broke overnight fix-mode runs: MANY concurrent `gipity` processes
|
|
75
127
|
* (relay daemon, file-sync hook, parallel commands) sharing one ~/.gipity/auth.json.
|
|
76
128
|
* Refresh tokens are SINGLE-USE — the server rotates them, so when several siblings
|
|
77
|
-
* race to refresh the same token, the first wins and the rest get a 401.
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* retry the race/transient failures, re-reading each attempt so we ADOPT whatever
|
|
82
|
-
* token a sibling just rotated in rather than resubmitting the rotated-away one.
|
|
129
|
+
* race to refresh the same token, the first wins and the rest get a 401. We guard
|
|
130
|
+
* the refresh with a cross-process lock so only one sibling refreshes at a time; the
|
|
131
|
+
* others wait, re-read the file, and ADOPT the freshly-rotated token. If the lock
|
|
132
|
+
* can't be had in time we still try (re-reading first), so this never hangs a command.
|
|
83
133
|
* Stays void / never throws / never clears auth: a genuine dead token still flows to
|
|
84
134
|
* the caller's existing 401 path (which messages "run: gipity login"). */
|
|
85
135
|
export async function refreshTokenIfNeeded() {
|
|
@@ -90,57 +140,71 @@ export async function refreshTokenIfNeeded() {
|
|
|
90
140
|
const buffer = 5 * 60 * 1000; // refresh 5 min before the access token lapses
|
|
91
141
|
const fresh = (a) => Date.now() <= new Date(a.expiresAt).getTime() - buffer;
|
|
92
142
|
if (fresh(auth))
|
|
93
|
-
return;
|
|
143
|
+
return; // fast path: token still good, no lock needed
|
|
94
144
|
// If the refresh token itself has expired, re-login is genuinely required; leave the
|
|
95
145
|
// expired auth in place so the caller's existing 401 path prompts `gipity login`.
|
|
96
146
|
const refreshExp = decodeJwtExp(auth.refreshToken);
|
|
97
147
|
if (refreshExp && Date.now() > refreshExp * 1000)
|
|
98
148
|
return;
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
149
|
+
// Serialize with sibling processes so we don't race the single-use refresh token.
|
|
150
|
+
const release = await acquireRefreshLock();
|
|
151
|
+
try {
|
|
152
|
+
// Re-check under the lock: a sibling may have refreshed while we waited.
|
|
153
|
+
const held = readAuthFresh();
|
|
154
|
+
if (held && fresh(held)) {
|
|
155
|
+
cached = held;
|
|
105
156
|
return;
|
|
106
157
|
}
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
158
|
+
const { resolveApiBase } = await import('./config.js');
|
|
159
|
+
const apiBase = resolveApiBase();
|
|
160
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
161
|
+
const cur = readAuthFresh(); // a sibling may have just refreshed for us
|
|
162
|
+
if (cur && fresh(cur)) {
|
|
163
|
+
cached = cur;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const refreshToken = cur?.refreshToken ?? auth.refreshToken;
|
|
167
|
+
let res;
|
|
168
|
+
try {
|
|
169
|
+
res = await fetch(`${apiBase}/auth/refresh`, {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: { 'Content-Type': 'application/json' },
|
|
172
|
+
body: JSON.stringify({ refreshToken }),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
124
176
|
await delay(attempt * 300);
|
|
125
|
-
continue;
|
|
177
|
+
continue; // network blip - retry
|
|
126
178
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (after && fresh(after)) {
|
|
136
|
-
cached = after;
|
|
179
|
+
if (res.ok) {
|
|
180
|
+
const json = await res.json().catch(() => null);
|
|
181
|
+
const exp = json && decodeJwtExp(json.accessToken);
|
|
182
|
+
if (!json || !exp) {
|
|
183
|
+
await delay(attempt * 300);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
saveAuth({ accessToken: json.accessToken, refreshToken: json.refreshToken, email: auth.email, expiresAt: new Date(exp * 1000).toISOString() });
|
|
137
187
|
return;
|
|
138
188
|
}
|
|
139
|
-
|
|
189
|
+
// 401/403 → the refresh token was rejected outright (rotated away by a sibling
|
|
190
|
+
// that beat us to the lock, or genuinely expired). Re-read once more in case a
|
|
191
|
+
// sibling's fresh token just landed; otherwise stop and let the caller re-login.
|
|
192
|
+
if (res.status === 401 || res.status === 403) {
|
|
193
|
+
const after = readAuthFresh();
|
|
194
|
+
if (after && fresh(after)) {
|
|
195
|
+
cached = after;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
await delay(attempt * 300); // 5xx / unexpected → transient, retry
|
|
140
201
|
}
|
|
141
|
-
|
|
202
|
+
// Retries exhausted: leave the existing token. The caller's request will 401 and the
|
|
203
|
+
// existing handler messages the user — we never delete the shared auth.json here.
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
if (release)
|
|
207
|
+
release();
|
|
142
208
|
}
|
|
143
|
-
// Retries exhausted: leave the existing token. The caller's request will 401 and the
|
|
144
|
-
// existing handler messages the user — we never delete the shared auth.json here.
|
|
145
209
|
}
|
|
146
210
|
//# sourceMappingURL=auth.js.map
|
package/dist/commands/claude.js
CHANGED
|
@@ -17,7 +17,7 @@ function resolveCommand(cmd) {
|
|
|
17
17
|
return `${cmd}.cmd`;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
import { getAuth, saveAuth
|
|
20
|
+
import { getAuth, saveAuth } from '../auth.js';
|
|
21
21
|
import { get, post, publicPost, ApiError, getAccountSlug } from '../api.js';
|
|
22
22
|
import { getConfig, saveConfigAt, clearConfigCache, getApiBaseOverride, DEFAULT_API_BASE, getConfigPath } from '../config.js';
|
|
23
23
|
import { sync } from '../sync.js';
|
|
@@ -339,7 +339,9 @@ export const claudeCommand = new Command('claude')
|
|
|
339
339
|
}
|
|
340
340
|
catch (err) {
|
|
341
341
|
if (err instanceof ApiError && err.statusCode === 401) {
|
|
342
|
-
clearAuth()
|
|
342
|
+
// Don't clearAuth() — the file is shared with concurrent gipity
|
|
343
|
+
// processes (sync hook, relay); deleting it logs them all out. A
|
|
344
|
+
// re-login overwrites it anyway. Leave it and just point the user.
|
|
343
345
|
console.error(` ${clrError('Your session expired.')} ${muted('Run: gipity login')}`);
|
|
344
346
|
}
|
|
345
347
|
else {
|
|
@@ -480,7 +482,9 @@ export const claudeCommand = new Command('claude')
|
|
|
480
482
|
process.exit(1);
|
|
481
483
|
}
|
|
482
484
|
if (err instanceof ApiError && err.statusCode === 401 && !reauthed && !nonInteractive) {
|
|
483
|
-
clearAuth()
|
|
485
|
+
// No clearAuth(): interactiveLogin() saves fresh tokens over the
|
|
486
|
+
// file. Deleting it first would log out any sibling process (sync
|
|
487
|
+
// hook, relay) sharing this ~/.gipity/auth.json mid-run.
|
|
484
488
|
console.log(` ${muted('Your session expired. Let\'s sign you back in.')}\n`);
|
|
485
489
|
auth = await interactiveLogin();
|
|
486
490
|
console.log('');
|
|
@@ -488,7 +492,7 @@ export const claudeCommand = new Command('claude')
|
|
|
488
492
|
continue;
|
|
489
493
|
}
|
|
490
494
|
if (err instanceof ApiError && err.statusCode === 401) {
|
|
491
|
-
|
|
495
|
+
// Leave the shared auth.json in place (see above) — just message.
|
|
492
496
|
console.error(` ${clrError('Your session expired.')}`);
|
|
493
497
|
console.error(` ${muted('Run: gipity login')}`);
|
|
494
498
|
process.exit(1);
|
package/dist/commands/sync.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { sync } from '../sync.js';
|
|
3
3
|
import { createProgressReporter } from '../progress.js';
|
|
4
|
-
import { error as clrError } from '../colors.js';
|
|
4
|
+
import { error as clrError, muted } from '../colors.js';
|
|
5
5
|
export const syncCommand = new Command('sync')
|
|
6
6
|
.description('Sync files (a .gipityignore at the project root excludes paths, gitignore-style)')
|
|
7
7
|
.option('--plan', 'Print the plan without applying any changes')
|
|
8
8
|
.option('--force', 'Bypass the bulk-deletion guard')
|
|
9
|
+
.option('--prune', 'Remove files that exist on Gipity but not locally (applies the bulk deletes the guard defers)')
|
|
9
10
|
.option('--json', 'Output as JSON')
|
|
10
11
|
.action(async (opts) => {
|
|
11
12
|
try {
|
|
12
13
|
// JSON mode stays machine-clean; otherwise show live progress on a TTY.
|
|
13
14
|
const progress = opts.json ? undefined : createProgressReporter();
|
|
14
|
-
const result = await sync({ plan: opts.plan, force: opts.force, progress });
|
|
15
|
+
const result = await sync({ plan: opts.plan, force: opts.force, prune: opts.prune, progress });
|
|
15
16
|
if (opts.json) {
|
|
16
17
|
console.log(JSON.stringify(result));
|
|
17
18
|
}
|
|
@@ -20,8 +21,14 @@ export const syncCommand = new Command('sync')
|
|
|
20
21
|
if (!opts.plan && result.applied > 0) {
|
|
21
22
|
console.log(`\n${result.applied} action${result.applied > 1 ? 's' : ''} applied.`);
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
// The guard defers bulk server-side deletes (files on Gipity but not
|
|
25
|
+
// local). Surface that here as a helpful hint - not a red error - with
|
|
26
|
+
// the one command that resolves it. Only `gipity sync` shows this;
|
|
27
|
+
// deploy/test/sandbox stay silent (their internal sync defers quietly).
|
|
28
|
+
if (result.deferredDeletes > 0) {
|
|
29
|
+
console.log(muted(`\n${result.deferredDeletes} file${result.deferredDeletes > 1 ? 's' : ''} on Gipity ` +
|
|
30
|
+
`${result.deferredDeletes > 1 ? 'are' : 'is'} not present locally and ${result.deferredDeletes > 1 ? 'were' : 'was'} left untouched. ` +
|
|
31
|
+
`Run 'gipity sync --prune' to remove ${result.deferredDeletes > 1 ? 'them' : 'it'}.`));
|
|
25
32
|
}
|
|
26
33
|
for (const e of result.errors)
|
|
27
34
|
console.error(clrError(e));
|
package/dist/knowledge.js
CHANGED
|
@@ -45,7 +45,7 @@ Gipity is the cloud platform your project runs on - hosting, databases, deployme
|
|
|
45
45
|
|
|
46
46
|
Prefer the cheapest option that works - CLI and sandbox are instant and free, app services are runtime HTTP calls, \`gipity chat\` burns LLM tokens:
|
|
47
47
|
|
|
48
|
-
1. CLI commands (fast, no agent overhead). The \`gipity\` CLI covers add, deploy, db, fn, logs, browser, sync, memory, skill, and more. All commands support \`--json\`.
|
|
48
|
+
1. CLI commands (fast, no agent overhead). The \`gipity\` CLI covers add, deploy, db, fn, logs, browser, sync, memory, skill, email, and more. All commands support \`--json\`. You can send email yourself - \`gipity email send\` goes out as the agent from \`gipity@gipity.ai\` with no setup or API keys (\`gipity skill read email\`); don't build a \`mailto:\` workaround or reach for an SMTP library.
|
|
49
49
|
2. Cloud sandbox via \`gipity sandbox run\` - Docker container with pre-installed tools for media (ffmpeg, ImageMagick, sox), documents (pandoc, LibreOffice), and data (pandas, matplotlib, sqlite3). Run \`gipity skill read sandbox-tools\` for the full toolkit. No network from inside the sandbox - fetch what you need before sending it in.
|
|
50
50
|
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. 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|music>\` or \`gipity chat\`. \`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.
|
|
51
51
|
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.
|
|
@@ -54,7 +54,7 @@ You are the developer. Write files in this directory - the Gipity Claude Code pl
|
|
|
54
54
|
|
|
55
55
|
## Use first-party services before reaching outside
|
|
56
56
|
|
|
57
|
-
Gipity ships first-party services for what apps usually pull from third parties - auth, location/geocoding, LLM, image/audio/video generation, transcription, file uploads, realtime. Before calling an external API or adding an npm package for one of these, check \`gipity skill list\` for a match. First-party services need no API keys, cost less, and keep data in-house. Reach outside only when the catalog has no equivalent - and say so when you do.
|
|
57
|
+
Gipity ships first-party services for what apps usually pull from third parties - auth, location/geocoding, LLM, image/audio/video generation, transcription, file uploads, realtime, and email (send as the agent via \`gipity email send\`, or from a deployed app via a workflow \`notify\` step - no SMTP/SendGrid/Nodemailer). Before calling an external API or adding an npm package for one of these, check \`gipity skill list\` for a match. First-party services need no API keys, cost less, and keep data in-house. Reach outside only when the catalog has no equivalent - and say so when you do.
|
|
58
58
|
|
|
59
59
|
## Don't guess Gipity facts - look them up
|
|
60
60
|
|
|
@@ -98,7 +98,7 @@ mkdir -p ~/GipityProjects/<slug> && cd ~/GipityProjects/<slug> && gipity init <s
|
|
|
98
98
|
|
|
99
99
|
## CLI quick reference
|
|
100
100
|
|
|
101
|
-
Key commands: \`gipity add <template|kit>\`, \`gipity deploy dev\`, \`gipity sandbox run\`, \`gipity page inspect <url>\`, \`gipity page screenshot <url>\`, \`gipity db query "SQL"\`, \`gipity fn call <name>\`, \`gipity logs fn <name>\`, \`gipity skill read <name>\`.
|
|
101
|
+
Key commands: \`gipity add <template|kit>\`, \`gipity deploy dev\`, \`gipity sandbox run\`, \`gipity page inspect <url>\`, \`gipity page screenshot <url>\`, \`gipity db query "SQL"\`, \`gipity fn call <name>\`, \`gipity logs fn <name>\`, \`gipity email send --to <addr> --subject <s> --body <b>\` (sends as \`gipity@gipity.ai\`; omit \`--to\` to self-send), \`gipity skill read <name>\`.
|
|
102
102
|
Pull an existing remote project local (given its URL/slug): \`mkdir -p ~/GipityProjects/<slug> && cd ~/GipityProjects/<slug> && gipity init <slug>\` (adopts the matching project and syncs files down - this is the "clone").
|
|
103
103
|
For deterministic text questions (letter/word counts, substring occurrences, nth word/char, anagrams), use \`gipity text analyze "<text>"\` - local and instant, no sandbox or LLM needed.
|
|
104
104
|
Run \`gipity --help\` for the full list. Use \`--help\` on any command for details.
|
|
@@ -149,6 +149,7 @@ Kit skills (reusable building blocks - \`gipity add <kit>\`):
|
|
|
149
149
|
- \`chatbot\` - the chatbot kit: persona + scope guardrails + static knowledge, bubble widget or headless engine
|
|
150
150
|
|
|
151
151
|
Other key skills:
|
|
152
|
+
- \`email\` - sending email as the agent from gipity@gipity.ai (no setup/keys) — plus Gmail-thread replies, HTML formatting, images
|
|
152
153
|
- \`sandbox-tools\` - cloud sandbox capabilities and pre-installed tools
|
|
153
154
|
- \`tts\` - agent-side speech tools (different from the \`app-tts\` HTTP service)`;
|
|
154
155
|
export const DEFINITION_OF_DONE = `## Definition of done (build tasks)
|
package/dist/sync.js
CHANGED
|
@@ -481,16 +481,20 @@ async function bulkDeleteGuard(p, knownFiles, opts) {
|
|
|
481
481
|
const totalDeletes = p.deletesLocal + p.deletesRemote;
|
|
482
482
|
if (totalDeletes === 0)
|
|
483
483
|
return true;
|
|
484
|
-
if (opts.force)
|
|
484
|
+
if (opts.force || opts.prune)
|
|
485
485
|
return true;
|
|
486
486
|
const denom = Math.max(knownFiles, 1);
|
|
487
487
|
const fraction = totalDeletes / denom;
|
|
488
488
|
if (totalDeletes < BULK_DELETE_COUNT || fraction < BULK_DELETE_FRACTION)
|
|
489
489
|
return true;
|
|
490
490
|
if (!opts.interactive || getAutoConfirm()) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
491
|
+
// Non-interactive (deploy/test/sandbox auto-mirror): there's no human to
|
|
492
|
+
// confirm, so defer the bulk deletes SILENTLY and carry them forward. This
|
|
493
|
+
// is the safe default - uploads/downloads still apply, nothing is deleted -
|
|
494
|
+
// and it avoids spamming a "Refusing to delete N files" error on every
|
|
495
|
+
// command when a project legitimately has server-only files (e.g. runtime
|
|
496
|
+
// uploads, or sandbox outputs not kept locally). The deferred count rides
|
|
497
|
+
// out in SyncResult.deferredDeletes; `gipity sync --prune` applies them.
|
|
494
498
|
return false;
|
|
495
499
|
}
|
|
496
500
|
const answer = await prompt(`\nPlan deletes ${totalDeletes} files (${Math.round(fraction * 100)}% of the tree). Type "delete" to confirm: `);
|
|
@@ -572,6 +576,7 @@ async function syncInner(projectGuid, root, ignore, opts, interactive) {
|
|
|
572
576
|
return {
|
|
573
577
|
plan: planned, applied: 0, skipped: 0, errors: [],
|
|
574
578
|
summary: formatPlan(planned),
|
|
579
|
+
deferredDeletes: 0,
|
|
575
580
|
};
|
|
576
581
|
}
|
|
577
582
|
// Bulk-delete guard over the *planned* deletes.
|
|
@@ -983,6 +988,7 @@ async function syncInner(projectGuid, root, ignore, opts, interactive) {
|
|
|
983
988
|
skipped: skippedByGuard,
|
|
984
989
|
errors,
|
|
985
990
|
summary: formatPlan(planned),
|
|
991
|
+
deferredDeletes: skippedByGuard,
|
|
986
992
|
};
|
|
987
993
|
}
|
|
988
994
|
function cleanupEmptyDirs(root, ignorePatterns) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gipity",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.397",
|
|
4
4
|
"description": "The full-stack platform tuned for AI agents. Database, storage, auth, functions, deploy, and drop-in kits - all agent-tuned. Pair with Claude Code or use standalone.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gipity": "dist/updater/shim.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"build": "tsc && chmod +x dist/index.js dist/gipcc.js dist/gipccd.js dist/updater/shim.js dist/updater/check.js",
|
|
13
13
|
"dev": "tsc --watch",
|
|
14
14
|
"test": "npm run test:smoke",
|
|
15
|
-
"test:smoke": "tsc && node --test dist/__tests__/utils.test.js dist/__tests__/colors.test.js dist/__tests__/config.test.js dist/__tests__/sync.test.js dist/__tests__/sync-apply.test.js dist/__tests__/sync-lock.test.js dist/__tests__/push-cas.test.js dist/__tests__/upload.test.js dist/__tests__/progress.test.js dist/__tests__/updater.test.js dist/__tests__/cli-smoke.test.js dist/__tests__/claude-noninteractive.test.js dist/__tests__/claude-trust.test.js dist/__tests__/relay-state.test.js dist/__tests__/relay-daemon.test.js dist/__tests__/relay-installers.test.js dist/__tests__/relay-bridge-abort.test.js dist/__tests__/relay-redact.test.js dist/__tests__/relay-machine-id.test.js dist/__tests__/stream-json.test.js dist/__tests__/relay-ingest-contract.test.js dist/__tests__/prompts.test.js dist/__tests__/capture-transcript.test.js dist/__tests__/flag-aliases.test.js dist/__tests__/adopt-cwd.test.js dist/__tests__/cli-cmd-agent.test.js dist/__tests__/cli-cmd-approval.test.js dist/__tests__/cli-cmd-audit.test.js dist/__tests__/cli-cmd-chat.test.js dist/__tests__/cli-cmd-credits.test.js dist/__tests__/cli-cmd-db.test.js dist/__tests__/cli-cmd-deploy.test.js dist/__tests__/cli-cmd-domain.test.js dist/__tests__/cli-cmd-email.test.js dist/__tests__/cli-cmd-file.test.js dist/__tests__/cli-cmd-fn.test.js dist/__tests__/cli-cmd-service.test.js dist/__tests__/cli-cmd-job.test.js dist/__tests__/cli-cmd-generate.test.js dist/__tests__/cli-cmd-gmail.test.js dist/__tests__/cli-cmd-info.test.js dist/__tests__/cli-cmd-init.test.js dist/__tests__/cli-cmd-location.test.js dist/__tests__/cli-cmd-text.test.js dist/__tests__/cli-cmd-login.test.js dist/__tests__/cli-cmd-logout.test.js dist/__tests__/cli-cmd-token.test.js dist/__tests__/cli-cmd-logs.test.js dist/__tests__/cli-cmd-memory.test.js dist/__tests__/cli-cmd-page.test.js dist/__tests__/cli-cmd-plan.test.js dist/__tests__/cli-cmd-project.test.js dist/__tests__/cli-cmd-rbac.test.js dist/__tests__/cli-cmd-realtime.test.js dist/__tests__/cli-cmd-records.test.js dist/__tests__/cli-cmd-relay.test.js dist/__tests__/cli-cmd-sandbox.test.js dist/__tests__/cli-cmd-add.test.js dist/__tests__/cli-cmd-remove.test.js dist/__tests__/cli-cmd-skill.test.js dist/__tests__/cli-cmd-test.test.js dist/__tests__/cli-cmd-workflow.test.js dist/__tests__/setup-skills-block.test.js dist/__tests__/setup-hooks.test.js",
|
|
15
|
+
"test:smoke": "tsc && node --test dist/__tests__/utils.test.js dist/__tests__/colors.test.js dist/__tests__/config.test.js dist/__tests__/sync.test.js dist/__tests__/sync-apply.test.js dist/__tests__/sync-lock.test.js dist/__tests__/auth-lock.test.js dist/__tests__/push-cas.test.js dist/__tests__/upload.test.js dist/__tests__/progress.test.js dist/__tests__/updater.test.js dist/__tests__/cli-smoke.test.js dist/__tests__/claude-noninteractive.test.js dist/__tests__/claude-trust.test.js dist/__tests__/relay-state.test.js dist/__tests__/relay-daemon.test.js dist/__tests__/relay-installers.test.js dist/__tests__/relay-bridge-abort.test.js dist/__tests__/relay-redact.test.js dist/__tests__/relay-machine-id.test.js dist/__tests__/stream-json.test.js dist/__tests__/relay-ingest-contract.test.js dist/__tests__/prompts.test.js dist/__tests__/capture-transcript.test.js dist/__tests__/flag-aliases.test.js dist/__tests__/adopt-cwd.test.js dist/__tests__/cli-cmd-agent.test.js dist/__tests__/cli-cmd-approval.test.js dist/__tests__/cli-cmd-audit.test.js dist/__tests__/cli-cmd-chat.test.js dist/__tests__/cli-cmd-credits.test.js dist/__tests__/cli-cmd-db.test.js dist/__tests__/cli-cmd-deploy.test.js dist/__tests__/cli-cmd-domain.test.js dist/__tests__/cli-cmd-email.test.js dist/__tests__/cli-cmd-file.test.js dist/__tests__/cli-cmd-fn.test.js dist/__tests__/cli-cmd-service.test.js dist/__tests__/cli-cmd-job.test.js dist/__tests__/cli-cmd-generate.test.js dist/__tests__/cli-cmd-gmail.test.js dist/__tests__/cli-cmd-info.test.js dist/__tests__/cli-cmd-init.test.js dist/__tests__/cli-cmd-location.test.js dist/__tests__/cli-cmd-text.test.js dist/__tests__/cli-cmd-login.test.js dist/__tests__/cli-cmd-logout.test.js dist/__tests__/cli-cmd-token.test.js dist/__tests__/cli-cmd-logs.test.js dist/__tests__/cli-cmd-memory.test.js dist/__tests__/cli-cmd-page.test.js dist/__tests__/cli-cmd-plan.test.js dist/__tests__/cli-cmd-project.test.js dist/__tests__/cli-cmd-rbac.test.js dist/__tests__/cli-cmd-realtime.test.js dist/__tests__/cli-cmd-records.test.js dist/__tests__/cli-cmd-relay.test.js dist/__tests__/cli-cmd-sandbox.test.js dist/__tests__/cli-cmd-add.test.js dist/__tests__/cli-cmd-remove.test.js dist/__tests__/cli-cmd-skill.test.js dist/__tests__/cli-cmd-test.test.js dist/__tests__/cli-cmd-workflow.test.js dist/__tests__/setup-skills-block.test.js dist/__tests__/setup-hooks.test.js",
|
|
16
16
|
"test:e2e": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-live.test.js dist/__tests__/cli-e2e-services-media-live.test.js dist/__tests__/cli-e2e-workflow-live.test.js dist/__tests__/cli-e2e-sandbox-live.test.js dist/__tests__/cli-e2e-page-fetch-live.test.js dist/__tests__/cli-e2e-page-test-live.test.js",
|
|
17
17
|
"test:e2e:sandbox": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-sandbox-live.test.js"
|
|
18
18
|
},
|