gipity 1.0.422 → 1.0.424

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/knowledge.js CHANGED
@@ -37,9 +37,9 @@ Kits are reusable building blocks added to an existing app, not whole templates
37
37
  - \`gipity add chatbot\` - Drop-in chatbot - configurable persona, scope guardrails, static knowledge (20k budget), streaming responses. Headless engine + bubble widget; bring your own UI if you want. Works in any app.
38
38
  - \`gipity add audio-align\` - Audio + lyrics -> word-level timing JSON. Demucs vocal isolation + MMS_FA forced alignment, runs as a Modal L4 GPU job (~$0.01 per 3-min song). For karaoke captions, subtitling, language learning, dubbing alignment.
39
39
  - \`gipity add i18n\` - Multi-language for web apps - language picker, locale persistence, RTL, plural/translation lookup. Scaffolds src/js/strings.js and wires it up; move your copy there and read it with t('key'). Web only.
40
- - \`gipity add records\` - Registry-driven records: declare objects/fields as data, get generic CRUD functions with validation, full-text search, soft delete, ACTOR provenance, and an audit event spine - every write is transactional (row + event). Field types include relations ({id,label}), currency, emails/phones/links composites. Ships backend functions + migrations. Needs a database (web-fullstack/api template).
41
- - \`gipity add views\` - Generic UI over records-kit objects: sortable/filterable table with full-text search, create/edit/delete forms with type-appropriate widgets, kanban board with drag-to-update. Renders entirely from the field registry - zero per-object UI code. Requires the records kit.
42
- - \`gipity add agent-api\` - Make your app agent-operable: named API keys (kit_api_keys) let agents and scripts write through the records kit's single write path with AGENT/API actor attribution - machine writes land on the same audit spine as human edits. Requires the records kit.
40
+ - \`gipity add records\` - Registry-driven records: declare objects/fields as data, get generic CRUD functions with validation, full-text search, soft delete, ACTOR provenance, and an audit event spine - every write is transactional (row + event). Field types include relations ({id,label}), currency, emails/phones/links composites. Ships backend functions + migrations. Needs a database (web-fullstack/api template). See the \`app-records\` skill.
41
+ - \`gipity add views\` - Generic UI over records-kit objects: sortable/filterable table with full-text search, create/edit/delete forms with type-appropriate widgets, kanban board with drag-to-update. Renders entirely from the field registry - zero per-object UI code. Requires the records kit. See the \`app-records\` skill.
42
+ - \`gipity add agent-api\` - Make your app agent-operable: named API keys (kit_api_keys) let agents and scripts write through the records kit's single write path with AGENT/API actor attribution - machine writes land on the same audit spine as human edits. Requires the records kit. See the \`app-records\` skill.
43
43
  - \`gipity add contacts\` - Source-agnostic contact data layer for lead-gen/CRM apps: import people from LinkedIn CSV + Gmail + pasted lists, resolve duplicates into one person while keeping EVERY value from every source with provenance (multi-valued attributes, never overwrites). Exact email/URL auto-merge; fuzzy name+company goes to a human merge-review queue (reversible). Re-imports detect job changes and emit signals. User-definable tags, full-text search, and a transactional event spine. Ships backend functions + migrations. Needs a database (web-fullstack/api template).
44
44
  - \`gipity add stripe\` - Charge your app's end-users for one-time purchases and subscriptions via Stripe. Owner connects their own Stripe account through Gipity-hosted onboarding (no API keys to paste); money lands in their account, Gipity takes a small platform fee. Ships a buy-button / pricing component, a subscription-status helper for gating UI, a webhook-verified fulfillment function, and the payments/subscriptions tables. The platform brokers checkout + signature-verified webhooks. Needs a database (web-fullstack/api template).
45
45
  - \`gipity add notify\` - Web push notifications for any web app, including iOS home-screen web apps (iOS 16.4+). The platform owns the VAPID keys, encryption, and delivery — no keys to paste, no crypto, no server. Ships a <gipity-notify-button>, a service worker, and a PWA manifest; the device subscribes itself and self-heals stale subscriptions. Send from a function with the injected notify() service (one flat credit per send, owner-billed) or test with \`gipity notify test\`. Works in any template — no database required.`;
@@ -88,7 +88,7 @@ Prefer a live Gipity URL over an ephemeral artifact. When you'd otherwise reach
88
88
 
89
89
  The full "when to add a template" rule and the definition of done are spelled out in the two sections at the end of this document. In short: if the user wants something deployable (web app, game, API), \`gipity add <template>\` first (default \`web-simple\`); for a one-off task (analysis, PDFs, data work), use \`gipity sandbox run\` instead; to add a reusable building block to an existing app (e.g. multiplayer), \`gipity add <kit>\`.
90
90
 
91
- Build loop: \`gipity add\` → edit files → \`gipity deploy dev\` \`gipity page inspect <url>\` → fix any errors → repeat until the definition of done is met.
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
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"\`. Don't rewrite from memory of the template.
94
94
 
@@ -193,6 +193,7 @@ App development skills:
193
193
  - \`web-ui-patterns\` - default Gipity look (theme tokens) + web UI recipes - feeds, copy-to-clipboard
194
194
 
195
195
  Kit skills (reusable building blocks - \`gipity add <kit>\`):
196
+ - \`app-records\` - the records + views kits: registry-driven CRUD with search + audit history, and generated tables/kanban/forms (gipity add records)
196
197
  - \`audio-align\` - the audio-align kit: forced alignment of audio + lyrics into word-level timing JSON
197
198
  - \`chatbot\` - the chatbot kit: persona + scope guardrails + static knowledge, bubble widget or headless engine
198
199
 
@@ -202,7 +203,7 @@ Other key skills:
202
203
  - \`tts\` - agent-side speech tools (different from the \`app-tts\` HTTP service)`;
203
204
  export const DEFINITION_OF_DONE = `## Definition of done (build tasks)
204
205
  1. \`gipity deploy dev\` succeeds and you have a live URL.
205
- 2. \`gipity page inspect <url>\` returns no console errors and the page loads (HTTP 200, no blank screen).
206
+ 2. \`gipity page inspect <url>\` returns no console errors and the page loads (HTTP 200, no blank screen). (\`gipity deploy dev --inspect\` covers 1 and 2 in one command.)
206
207
  3. For apps with functions: \`gipity test\` passes.
207
208
  4. Non-rendered files the task called for (\`llms.txt\`, \`AGENTS.md\`, \`SKILL.md\`, \`robots.txt\`, served JSON, etc.): \`page inspect\` only sees rendered HTML, so verify them with \`gipity page fetch <url> <files...>\`. It flags any that 404 or come back as the static-host shell (a missing file is served as \`index.html\` with a 200, so a bare status check would pass) and checks each \`content-type\`.
208
209
  5. You told the user the live URL.
package/dist/sync.js CHANGED
@@ -664,6 +664,35 @@ export function effectiveIgnore(root, configIgnore) {
664
664
  const base = configIgnore && configIgnore.length ? configIgnore : DEFAULT_SYNC_IGNORE;
665
665
  return [...base, GIPITY_IGNORE_FILE, ...readGipityIgnore(root)];
666
666
  }
667
+ /** Fast, LOCAL-ONLY dirtiness probe: true when every local file matches its
668
+ * baseline entry by size+mtime and nothing was added or deleted since the
669
+ * last sync. Lets pre-action syncs (deploy) skip their server round trip:
670
+ * deploy reads server-side (VFS) state, so with nothing to push the sync
671
+ * adds no correctness, only latency. Conservative on purpose - any size or
672
+ * mtime movement reads as dirty (no hashing here), never-synced projects
673
+ * read as dirty, and callers then run the real sync(). */
674
+ export function isLocalTreeClean() {
675
+ try {
676
+ const config = requireConfig();
677
+ const root = projectDir();
678
+ const baseline = readBaseline(config.projectGuid);
679
+ if (!baseline.lastFullSync)
680
+ return false;
681
+ const local = walkLocal(root, effectiveIgnore(root, config.ignore), baseline.files);
682
+ const baselineFiles = baseline.files;
683
+ if (local.size !== Object.keys(baselineFiles).length)
684
+ return false;
685
+ for (const [path, info] of local) {
686
+ const prior = baselineFiles[path];
687
+ if (!prior || prior.size !== info.size || prior.mtime !== info.mtime)
688
+ return false;
689
+ }
690
+ return true;
691
+ }
692
+ catch {
693
+ return false;
694
+ }
695
+ }
667
696
  export async function sync(opts = {}) {
668
697
  const config = requireConfig();
669
698
  const root = projectDir();
@@ -1,101 +1,176 @@
1
1
  #!/usr/bin/env node
2
- // Background updater. Invoked detached by the shim; can also be invoked
3
- // directly by `gipity update --force`.
4
- import { appendFileSync, existsSync } from 'fs';
5
- import { LOCAL_DIR, LOCAL_ENTRY, UPDATE_LOG, readState, writeState, updatesDisabled } from './state.js';
6
- import { resolveCommand, spawnSyncCommand } from '../platform.js';
7
- const CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
2
+ import { createRequire as __cliCreateRequire } from 'module';
3
+ const require = __cliCreateRequire(import.meta.url);
4
+
5
+ // src/updater/check.ts
6
+ import { appendFileSync, existsSync as existsSync2 } from "fs";
7
+
8
+ // src/updater/state.ts
9
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
10
+ import { join } from "path";
11
+ import { homedir } from "os";
12
+ var GIPITY_DIR = join(homedir(), ".gipity");
13
+ var LOCAL_DIR = join(GIPITY_DIR, "local");
14
+ var LOCAL_PKG_DIR = join(LOCAL_DIR, "node_modules", "gipity");
15
+ var LOCAL_ENTRY = join(LOCAL_PKG_DIR, "dist", "index.js");
16
+ var STATE_FILE = join(GIPITY_DIR, "update-state.json");
17
+ var SETTINGS_FILE = join(GIPITY_DIR, "settings.json");
18
+ var UPDATE_LOG = join(GIPITY_DIR, "update.log");
19
+ var DEFAULT_STATE = {
20
+ installedVersion: null,
21
+ lastCheckAt: 0,
22
+ lastError: null,
23
+ updateChannel: "stable"
24
+ };
25
+ var DEFAULT_SETTINGS = {
26
+ autoUpdates: true
27
+ };
28
+ function ensureDir() {
29
+ mkdirSync(GIPITY_DIR, { recursive: true });
30
+ }
31
+ function readState() {
32
+ if (!existsSync(STATE_FILE)) return { ...DEFAULT_STATE };
33
+ try {
34
+ return { ...DEFAULT_STATE, ...JSON.parse(readFileSync(STATE_FILE, "utf-8")) };
35
+ } catch {
36
+ return { ...DEFAULT_STATE };
37
+ }
38
+ }
39
+ function writeState(state) {
40
+ ensureDir();
41
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
42
+ }
43
+ function readSettings() {
44
+ if (!existsSync(SETTINGS_FILE)) return { ...DEFAULT_SETTINGS };
45
+ try {
46
+ return { ...DEFAULT_SETTINGS, ...JSON.parse(readFileSync(SETTINGS_FILE, "utf-8")) };
47
+ } catch {
48
+ return { ...DEFAULT_SETTINGS };
49
+ }
50
+ }
51
+ function updatesDisabled() {
52
+ if (process.env["DISABLE_AUTOUPDATER"] === "1") return { disabled: true, reason: "DISABLE_AUTOUPDATER=1" };
53
+ if (process.env["CI"]) return { disabled: true, reason: "CI environment" };
54
+ if (!readSettings().autoUpdates) return { disabled: true, reason: "autoUpdates: false in settings.json" };
55
+ return { disabled: false };
56
+ }
57
+
58
+ // src/platform.ts
59
+ import { execSync, spawn, spawnSync } from "child_process";
60
+ function resolveCommand(cmd) {
61
+ if (process.platform !== "win32") return cmd;
62
+ try {
63
+ const lines = execSync(`where ${cmd}`, { encoding: "utf-8", windowsHide: true }).split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
64
+ return lines.find((l) => l.endsWith(".exe")) || lines.find((l) => l.endsWith(".cmd")) || cmd;
65
+ } catch {
66
+ return `${cmd}.cmd`;
67
+ }
68
+ }
69
+ function isBatchShim(cmd) {
70
+ return process.platform === "win32" && /\.(cmd|bat)$/i.test(cmd);
71
+ }
72
+ function winBatchInvocation(cmd, args) {
73
+ const comspec = process.env.ComSpec || "cmd.exe";
74
+ const quote = (t) => `"${String(t).replace(/"/g, '""')}"`;
75
+ const line = [cmd, ...args].map(quote).join(" ");
76
+ return { file: comspec, args: ["/d", "/s", "/c", `"${line}"`] };
77
+ }
78
+ function spawnSyncCommand(cmd, args = [], options = {}) {
79
+ if (isBatchShim(cmd)) {
80
+ const inv = winBatchInvocation(cmd, args);
81
+ return spawnSync(inv.file, inv.args, { windowsHide: true, ...options, windowsVerbatimArguments: true });
82
+ }
83
+ return spawnSync(cmd, [...args], { windowsHide: true, ...options });
84
+ }
85
+
86
+ // src/updater/check.ts
87
+ var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
8
88
  function log(line) {
9
- try {
10
- appendFileSync(UPDATE_LOG, `[${new Date().toISOString()}] ${line}\n`);
11
- }
12
- catch { /* ignore */ }
89
+ try {
90
+ appendFileSync(UPDATE_LOG, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${line}
91
+ `);
92
+ } catch {
93
+ }
13
94
  }
14
- export function compareSemver(a, b) {
15
- const pa = a.split('.').map(Number);
16
- const pb = b.split('.').map(Number);
17
- for (let i = 0; i < 3; i++) {
18
- const da = pa[i] ?? 0;
19
- const db = pb[i] ?? 0;
20
- if (da !== db)
21
- return da - db;
22
- }
23
- return 0;
95
+ function compareSemver(a, b) {
96
+ const pa = a.split(".").map(Number);
97
+ const pb = b.split(".").map(Number);
98
+ for (let i = 0; i < 3; i++) {
99
+ const da = pa[i] ?? 0;
100
+ const db = pb[i] ?? 0;
101
+ if (da !== db) return da - db;
102
+ }
103
+ return 0;
24
104
  }
25
105
  async function fetchLatestVersion() {
26
- const res = await fetch('https://registry.npmjs.org/gipity/latest', {
27
- headers: { accept: 'application/json' },
28
- });
29
- if (!res.ok)
30
- throw new Error(`registry HTTP ${res.status}`);
31
- const json = await res.json();
32
- if (!json.version)
33
- throw new Error('no version in registry response');
34
- return json.version;
106
+ const res = await fetch("https://registry.npmjs.org/gipity/latest", {
107
+ headers: { accept: "application/json" }
108
+ });
109
+ if (!res.ok) throw new Error(`registry HTTP ${res.status}`);
110
+ const json = await res.json();
111
+ if (!json.version) throw new Error("no version in registry response");
112
+ return json.version;
35
113
  }
36
114
  function installVersion(version) {
37
- // --ignore-scripts: this runs unattended in the background, so don't let a
38
- // compromised package's install lifecycle hooks execute. gipity ships
39
- // precompiled (dist/) and its deps need no build step, so nothing is lost.
40
- const res = spawnSyncCommand(resolveCommand('npm'), ['install', '--silent', '--no-audit', '--no-fund', '--ignore-scripts', `gipity@${version}`], {
41
- cwd: LOCAL_DIR,
42
- stdio: 'ignore',
43
- });
44
- if (res.error)
45
- log(`npm spawn failed: ${res.error.message}`);
46
- return res.status === 0 && existsSync(LOCAL_ENTRY);
115
+ const res = spawnSyncCommand(resolveCommand("npm"), ["install", "--silent", "--no-audit", "--no-fund", "--ignore-scripts", `gipity@${version}`], {
116
+ cwd: LOCAL_DIR,
117
+ stdio: "ignore"
118
+ });
119
+ if (res.error) log(`npm spawn failed: ${res.error.message}`);
120
+ return res.status === 0 && existsSync2(LOCAL_ENTRY);
47
121
  }
48
- export async function runCheck(opts = {}) {
49
- const state = readState();
50
- if (!opts.force) {
51
- const dis = updatesDisabled();
52
- if (dis.disabled) {
53
- log(`skipped: ${dis.reason}`);
54
- return { updated: false, reason: dis.reason };
55
- }
56
- if (Date.now() - state.lastCheckAt < CHECK_INTERVAL_MS) {
57
- return { updated: false, reason: 'cache-fresh' };
58
- }
59
- }
60
- let latest;
61
- try {
62
- latest = await fetchLatestVersion();
63
- }
64
- catch (e) {
65
- const msg = e instanceof Error ? e.message : String(e);
66
- state.lastError = `fetch failed: ${msg}`;
67
- state.lastCheckAt = Date.now();
68
- writeState(state);
69
- log(state.lastError);
70
- return { updated: false, reason: state.lastError };
122
+ async function runCheck(opts = {}) {
123
+ const state = readState();
124
+ if (!opts.force) {
125
+ const dis = updatesDisabled();
126
+ if (dis.disabled) {
127
+ log(`skipped: ${dis.reason}`);
128
+ return { updated: false, reason: dis.reason };
71
129
  }
72
- const current = state.installedVersion ?? '0.0.0';
73
- if (compareSemver(latest, current) <= 0) {
74
- state.lastError = null;
75
- state.lastCheckAt = Date.now();
76
- writeState(state);
77
- log(`up-to-date (current=${current}, latest=${latest})`);
78
- return { updated: false, reason: 'up-to-date' };
130
+ if (Date.now() - state.lastCheckAt < CHECK_INTERVAL_MS) {
131
+ return { updated: false, reason: "cache-fresh" };
79
132
  }
80
- log(`upgrading ${current} → ${latest}`);
81
- const ok = installVersion(latest);
133
+ }
134
+ let latest;
135
+ try {
136
+ latest = await fetchLatestVersion();
137
+ } catch (e) {
138
+ const msg = e instanceof Error ? e.message : String(e);
139
+ state.lastError = `fetch failed: ${msg}`;
82
140
  state.lastCheckAt = Date.now();
83
- if (ok) {
84
- state.installedVersion = latest;
85
- state.lastError = null;
86
- writeState(state);
87
- log(`upgraded to ${latest}`);
88
- return { updated: true, from: current, to: latest };
89
- }
90
- state.lastError = `npm install gipity@${latest} failed`;
91
141
  writeState(state);
92
142
  log(state.lastError);
93
143
  return { updated: false, reason: state.lastError };
144
+ }
145
+ const current = state.installedVersion ?? "0.0.0";
146
+ if (compareSemver(latest, current) <= 0) {
147
+ state.lastError = null;
148
+ state.lastCheckAt = Date.now();
149
+ writeState(state);
150
+ log(`up-to-date (current=${current}, latest=${latest})`);
151
+ return { updated: false, reason: "up-to-date" };
152
+ }
153
+ log(`upgrading ${current} \u2192 ${latest}`);
154
+ const ok = installVersion(latest);
155
+ state.lastCheckAt = Date.now();
156
+ if (ok) {
157
+ state.installedVersion = latest;
158
+ state.lastError = null;
159
+ writeState(state);
160
+ log(`upgraded to ${latest}`);
161
+ return { updated: true, from: current, to: latest };
162
+ }
163
+ state.lastError = `npm install gipity@${latest} failed`;
164
+ writeState(state);
165
+ log(state.lastError);
166
+ return { updated: false, reason: state.lastError };
94
167
  }
95
- // Direct invocation (detached background)
96
- const isDirect = import.meta.url === `file://${process.argv[1]}`;
168
+ var isDirect = import.meta.url === `file://${process.argv[1]}`;
97
169
  if (isDirect) {
98
- const force = process.argv.includes('--force');
99
- runCheck({ force }).catch((e) => log(`unhandled: ${e instanceof Error ? e.message : String(e)}`));
170
+ const force = process.argv.includes("--force");
171
+ runCheck({ force }).catch((e) => log(`unhandled: ${e instanceof Error ? e.message : String(e)}`));
100
172
  }
101
- //# sourceMappingURL=check.js.map
173
+ export {
174
+ compareSemver,
175
+ runCheck
176
+ };
@@ -1,78 +1,160 @@
1
1
  #!/usr/bin/env node
2
- // Thin launcher. Resolves the user-local install at ~/.gipity/local/, exec's
3
- // it, and kicks off a detached background updater. Modeled on Claude Code.
4
- import { spawnCommand, spawnSyncCommand } from '../platform.js';
5
- import { existsSync, readFileSync } from 'fs';
6
- import { fileURLToPath } from 'url';
7
- import { dirname, resolve, join } from 'path';
8
- import { LOCAL_ENTRY } from './state.js';
9
- import { isBootstrapped, bootstrap } from './bootstrap.js';
10
- const __dirname = dirname(fileURLToPath(import.meta.url));
11
- const pkgRoot = resolve(__dirname, '..', '..');
12
- const shimPkg = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf-8'));
13
- // Dev-link detection. Published tarballs ship `dist/**` only (see package.json
14
- // `files`), so a sibling `src/` means `gipity` was `npm link`ed from a source
15
- // checkout. In that case run THIS checkout's own dist and skip the bootstrap +
16
- // background updater entirely - otherwise the shim would defer to the last
17
- // PUBLISHED build in ~/.gipity/local/ and silently shadow your local changes.
18
- // Nothing persistent is written, so `just cli-unlink` (npm unlink) is the only
19
- // cleanup needed: once `gipity` no longer resolves to this checkout, dev mode
20
- // stops applying on its own.
21
- const isDevLink = existsSync(join(pkgRoot, 'src'));
22
- function startBackgroundUpdater() {
23
- const checkScript = join(__dirname, 'check.js');
24
- try {
25
- const child = spawnCommand(process.execPath, [checkScript], {
26
- detached: true,
27
- stdio: 'ignore',
28
- env: process.env,
29
- });
30
- // Async spawn 'error' bypasses the try/catch; swallow so the best-effort
31
- // updater never crashes the CLI.
32
- child.on('error', () => { });
33
- child.unref();
2
+ import { createRequire as __cliCreateRequire } from 'module';
3
+ const require = __cliCreateRequire(import.meta.url);
4
+
5
+ // src/platform.ts
6
+ import { execSync, spawn, spawnSync } from "child_process";
7
+ function resolveCommand(cmd) {
8
+ if (process.platform !== "win32") return cmd;
9
+ try {
10
+ const lines = execSync(`where ${cmd}`, { encoding: "utf-8", windowsHide: true }).split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
11
+ return lines.find((l) => l.endsWith(".exe")) || lines.find((l) => l.endsWith(".cmd")) || cmd;
12
+ } catch {
13
+ return `${cmd}.cmd`;
14
+ }
15
+ }
16
+ function isBatchShim(cmd) {
17
+ return process.platform === "win32" && /\.(cmd|bat)$/i.test(cmd);
18
+ }
19
+ function winBatchInvocation(cmd, args) {
20
+ const comspec = process.env.ComSpec || "cmd.exe";
21
+ const quote = (t) => `"${String(t).replace(/"/g, '""')}"`;
22
+ const line = [cmd, ...args].map(quote).join(" ");
23
+ return { file: comspec, args: ["/d", "/s", "/c", `"${line}"`] };
24
+ }
25
+ function spawnCommand(cmd, args = [], options = {}) {
26
+ if (isBatchShim(cmd)) {
27
+ const inv = winBatchInvocation(cmd, args);
28
+ return spawn(inv.file, inv.args, { windowsHide: true, ...options, windowsVerbatimArguments: true });
29
+ }
30
+ return spawn(cmd, [...args], { windowsHide: true, ...options });
31
+ }
32
+ function spawnSyncCommand(cmd, args = [], options = {}) {
33
+ if (isBatchShim(cmd)) {
34
+ const inv = winBatchInvocation(cmd, args);
35
+ return spawnSync(inv.file, inv.args, { windowsHide: true, ...options, windowsVerbatimArguments: true });
36
+ }
37
+ return spawnSync(cmd, [...args], { windowsHide: true, ...options });
38
+ }
39
+
40
+ // src/updater/shim.ts
41
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
42
+ import { fileURLToPath, pathToFileURL } from "url";
43
+ import { dirname, resolve, join as join3 } from "path";
44
+
45
+ // src/updater/state.ts
46
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
47
+ import { join } from "path";
48
+ import { homedir } from "os";
49
+ var GIPITY_DIR = join(homedir(), ".gipity");
50
+ var LOCAL_DIR = join(GIPITY_DIR, "local");
51
+ var LOCAL_PKG_DIR = join(LOCAL_DIR, "node_modules", "gipity");
52
+ var LOCAL_ENTRY = join(LOCAL_PKG_DIR, "dist", "index.js");
53
+ var STATE_FILE = join(GIPITY_DIR, "update-state.json");
54
+ var SETTINGS_FILE = join(GIPITY_DIR, "settings.json");
55
+ var UPDATE_LOG = join(GIPITY_DIR, "update.log");
56
+ var DEFAULT_STATE = {
57
+ installedVersion: null,
58
+ lastCheckAt: 0,
59
+ lastError: null,
60
+ updateChannel: "stable"
61
+ };
62
+ function ensureDir() {
63
+ mkdirSync(GIPITY_DIR, { recursive: true });
64
+ }
65
+ function readState() {
66
+ if (!existsSync(STATE_FILE)) return { ...DEFAULT_STATE };
67
+ try {
68
+ return { ...DEFAULT_STATE, ...JSON.parse(readFileSync(STATE_FILE, "utf-8")) };
69
+ } catch {
70
+ return { ...DEFAULT_STATE };
71
+ }
72
+ }
73
+ function writeState(state) {
74
+ ensureDir();
75
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
76
+ }
77
+
78
+ // src/updater/bootstrap.ts
79
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
80
+ import { join as join2 } from "path";
81
+ function isBootstrapped() {
82
+ return existsSync2(LOCAL_ENTRY);
83
+ }
84
+ function bootstrap(version, quiet = false) {
85
+ mkdirSync2(LOCAL_DIR, { recursive: true });
86
+ const pkgJsonPath = join2(LOCAL_DIR, "package.json");
87
+ if (!existsSync2(pkgJsonPath)) {
88
+ writeFileSync2(pkgJsonPath, JSON.stringify({ name: "gipity-local", private: true, version: "0.0.0" }, null, 2));
89
+ }
90
+ if (!quiet) process.stderr.write(`Setting up gipity local install at ~/.gipity/local (one-time)...
91
+ `);
92
+ const res = spawnSyncCommand(resolveCommand("npm"), ["install", "--no-audit", "--no-fund", "--ignore-scripts", `gipity@${version}`], {
93
+ cwd: LOCAL_DIR,
94
+ stdio: ["ignore", "pipe", "pipe"],
95
+ encoding: "utf-8"
96
+ });
97
+ if (res.status !== 0 || !existsSync2(LOCAL_ENTRY)) {
98
+ if (!quiet) {
99
+ const stderr = (res.stderr || "").toString();
100
+ const notPublished = /E404|No matching version|notarget/i.test(stderr);
101
+ if (notPublished) {
102
+ process.stderr.write(`gipity v${version} is not yet published to npm - using the currently installed build.
103
+ `);
104
+ } else {
105
+ const firstLine = stderr.split("\n").map((l) => l.trim()).find((l) => l.length > 0) || (res.error ? res.error.message : `npm exit ${res.status}`);
106
+ const reason = firstLine.length > 160 ? firstLine.slice(0, 157) + "..." : firstLine;
107
+ process.stderr.write(`gipity: could not set up local install (${reason}). Using the currently installed build.
108
+ `);
109
+ }
34
110
  }
35
- catch { /* updater is best-effort, never block */ }
111
+ return false;
112
+ }
113
+ const state = readState();
114
+ state.installedVersion = version;
115
+ writeState(state);
116
+ if (!quiet) process.stderr.write(`Done.
117
+ `);
118
+ return true;
36
119
  }
37
- function execLocal() {
38
- const args = process.argv.slice(2);
39
- const res = spawnSyncCommand(process.execPath, [LOCAL_ENTRY, ...args], {
40
- stdio: 'inherit',
41
- env: process.env,
120
+
121
+ // src/updater/shim.ts
122
+ var __dirname = dirname(fileURLToPath(import.meta.url));
123
+ var pkgRoot = resolve(__dirname, "..", "..");
124
+ var shimPkg = JSON.parse(readFileSync2(join3(pkgRoot, "package.json"), "utf-8"));
125
+ var isDevLink = existsSync3(join3(pkgRoot, "src"));
126
+ function startBackgroundUpdater() {
127
+ const checkScript = join3(__dirname, "check.js");
128
+ try {
129
+ const child = spawnCommand(process.execPath, [checkScript], {
130
+ detached: true,
131
+ stdio: "ignore",
132
+ env: process.env
42
133
  });
43
- process.exit(res.status ?? 1);
44
- }
45
- function execSelf() {
46
- // Run our own dist/index.js (this checkout/install), bypassing the local
47
- // bootstrapped copy. Used for dev links and as the bootstrap-failure fallback
48
- // so the user is never blocked (they can retry via `gipity update --force`).
49
- const ownEntry = resolve(__dirname, '..', 'index.js');
50
- const args = process.argv.slice(2);
51
- const res = spawnSyncCommand(process.execPath, [ownEntry, ...args], {
52
- stdio: 'inherit',
53
- env: process.env,
134
+ child.on("error", () => {
54
135
  });
55
- process.exit(res.status ?? 1);
136
+ child.unref();
137
+ } catch {
138
+ }
139
+ }
140
+ async function runEntry(entry) {
141
+ try {
142
+ await import(pathToFileURL(entry).href);
143
+ } catch (err) {
144
+ console.error(err instanceof Error ? err.stack ?? err.message : String(err));
145
+ process.exit(1);
146
+ }
56
147
  }
57
- // Loud startup output (bootstrap status, npm-fallback notice) is reserved for
58
- // the handful of commands where users expect a visible chrome banner. All other
59
- // subcommands - scaffold, skills, fn, test, deploy, etc. - run silently so
60
- // their output stays clean in transcripts and agent tool results.
61
- const rawArgs = process.argv.slice(2);
62
- const firstArg = rawArgs[0];
63
- const isLoud = rawArgs.length === 0 ||
64
- firstArg === 'claude' ||
65
- firstArg === '--version' ||
66
- firstArg === '-v' ||
67
- firstArg === 'version';
68
- // Dev link: run this checkout's own build, no bootstrap, no auto-update.
69
- if (isDevLink)
70
- execSelf();
71
- if (!isBootstrapped()) {
72
- const ok = bootstrap(shimPkg.version, !isLoud);
73
- if (!ok)
74
- execSelf();
148
+ var execLocal = () => runEntry(LOCAL_ENTRY);
149
+ var execSelf = () => runEntry(resolve(__dirname, "..", "index.js"));
150
+ var rawArgs = process.argv.slice(2);
151
+ var firstArg = rawArgs[0];
152
+ var isLoud = rawArgs.length === 0 || firstArg === "claude" || firstArg === "--version" || firstArg === "-v" || firstArg === "version";
153
+ if (isDevLink) {
154
+ await execSelf();
155
+ } else if (!isBootstrapped() && !bootstrap(shimPkg.version, !isLoud)) {
156
+ await execSelf();
157
+ } else {
158
+ startBackgroundUpdater();
159
+ await execLocal();
75
160
  }
76
- startBackgroundUpdater();
77
- execLocal();
78
- //# sourceMappingURL=shim.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gipity",
3
- "version": "1.0.422",
3
+ "version": "1.0.424",
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",
@@ -9,11 +9,12 @@
9
9
  },
10
10
  "type": "module",
11
11
  "scripts": {
12
- "build": "tsc && chmod +x dist/index.js dist/gipcc.js dist/gipccd.js dist/updater/shim.js dist/updater/check.js",
12
+ "build": "tsc && node scripts/bundle.mjs && chmod +x dist/index.js dist/gipcc.js dist/gipccd.js dist/updater/shim.js dist/updater/check.js",
13
13
  "postbuild": "node scripts/gen-build-info.mjs",
14
+ "prepack": "npm run build",
14
15
  "dev": "tsc --watch",
15
16
  "test": "npm run test:smoke",
16
- "test:smoke": "tsc && node --test dist/__tests__/utils.test.js dist/__tests__/platform.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__/api-401-retry.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-diagnostics.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__/ingest-queue.test.js dist/__tests__/stream-delta.test.js dist/__tests__/phase-tracker.test.js dist/__tests__/relay-ingest-contract.test.js dist/__tests__/prompts.test.js dist/__tests__/capture-transcript.test.js dist/__tests__/capture-lock.test.js dist/__tests__/capture-resolve.test.js dist/__tests__/flag-aliases.test.js dist/__tests__/client-context.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-storage.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-setup.test.js dist/__tests__/cli-cmd-doctor.test.js dist/__tests__/claude-setup.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",
17
+ "test:smoke": "tsc && node --test dist/__tests__/utils.test.js dist/__tests__/platform.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-clean-check.test.js dist/__tests__/sync-lock.test.js dist/__tests__/auth-lock.test.js dist/__tests__/api-401-retry.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-diagnostics.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__/ingest-queue.test.js dist/__tests__/stream-delta.test.js dist/__tests__/phase-tracker.test.js dist/__tests__/relay-ingest-contract.test.js dist/__tests__/prompts.test.js dist/__tests__/capture-transcript.test.js dist/__tests__/capture-lock.test.js dist/__tests__/capture-resolve.test.js dist/__tests__/flag-aliases.test.js dist/__tests__/client-context.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-storage.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-setup.test.js dist/__tests__/cli-cmd-doctor.test.js dist/__tests__/claude-setup.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",
17
18
  "test:smoke:quick": "node scripts/smoke-quick.mjs",
18
19
  "test:e2e": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-live.test.js dist/__tests__/cli-e2e-sync-live.test.js dist/__tests__/cli-e2e-sync-stress-live.test.js dist/__tests__/cli-e2e-rollback-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",
19
20
  "test:e2e:sync": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-sync-live.test.js",
@@ -29,6 +30,7 @@
29
30
  "devDependencies": {
30
31
  "@types/node": "^20.0.0",
31
32
  "@types/tar-stream": "^3.1.4",
33
+ "esbuild": "^0.28.1",
32
34
  "typescript": "5.9.3"
33
35
  },
34
36
  "engines": {