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/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
The full-stack platform tuned for AI agents.
|
|
4
4
|
|
|
5
|
-
[Gipity](https://gipity.ai) is the platform: hosting, databases, file storage, deployment, workflows, code execution, and monitoring. Agent-tuned from idea to deploy. Use standalone, or pair with Claude Code to give
|
|
5
|
+
[Gipity](https://gipity.ai) is the platform: hosting, databases, file storage, deployment, workflows, code execution, and monitoring. Agent-tuned from idea to deploy. Use standalone, or pair with your coding agent - Claude Code, Codex, or Grok - to give it cloud superpowers. Any model, any infra, always your code.
|
|
6
6
|
|
|
7
|
-
This CLI connects [Claude Code](https://claude.ai/claude-code) to Gipity's cloud platform
|
|
7
|
+
This CLI connects your coding agent - [Claude Code](https://claude.ai/claude-code), Codex, or Grok - to Gipity's cloud platform: databases, deployment, browser testing, image gen, and 50+ other capabilities your local agent doesn't have. It also syncs files so your local agent and the Gipity web agent share the same project.
|
|
8
8
|
|
|
9
9
|
## Getting Started
|
|
10
10
|
|
|
11
|
-
One line installs everything. It sets up Node 18+ (if you don't already have it) and the Gipity CLI, with no sudo required:
|
|
11
|
+
**Step 1 - install.** One line installs everything. It sets up Node 18+ (if you don't already have it) and the Gipity CLI, with no sudo required:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
# macOS / Linux / WSL
|
|
@@ -18,13 +18,42 @@ curl -fsSL https://gipity.ai/install.sh | bash
|
|
|
18
18
|
irm https://gipity.ai/install.ps1 | iex
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
**Step 2 - pick your path.** There are three, and they mix freely:
|
|
22
|
+
|
|
23
|
+
| You want to... | Run |
|
|
24
|
+
|----------------|-----|
|
|
25
|
+
| Start building right now, from anywhere | `gipity build` |
|
|
26
|
+
| Use your own workflow in your own directory | `gipity init`, then `claude` / `codex` / `grok` |
|
|
27
|
+
| Drive this computer from gipity.ai (phone, browser) | `gipity connect` |
|
|
28
|
+
|
|
29
|
+
### `gipity build` - start from anywhere
|
|
30
|
+
|
|
31
|
+
One command does everything: logs you in (6-digit email code), lets you pick or create a project, lets you pick your coding agent (Claude Code, Codex, or Grok - it installs the agent if needed), and launches it with the whole Gipity stack wired up.
|
|
22
32
|
|
|
23
33
|
```bash
|
|
24
|
-
gipity
|
|
34
|
+
gipity build
|
|
25
35
|
```
|
|
26
36
|
|
|
27
|
-
`gipity
|
|
37
|
+
### `gipity init` - bring your own workflow
|
|
38
|
+
|
|
39
|
+
Prefer launching your agent yourself? From any project directory:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
gipity init
|
|
43
|
+
claude # or codex, or grok - whatever you use
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`init` links the directory to a Gipity project, writes CLAUDE.md/AGENTS.md primers so your agent understands Gipity, and installs the Gipity skills + file-sync hooks into the agent CLIs found on your machine (Claude Code, Codex, Grok; Cursor and Gemini get primer files too).
|
|
47
|
+
|
|
48
|
+
### `gipity connect` - drive it from the web
|
|
49
|
+
|
|
50
|
+
Want to start chats from gipity.ai (including your phone) and have them run the coding agent on this computer? Connect it once:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
gipity connect
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
It logs you in, pairs the machine, starts the Gipity relay in the background, and installs the login service so it survives reboots - then stops, without launching anything. From then on, open gipity.ai and start a chat to drive your agent here. Manage it with `gipity relay status` / `pause` / `resume` / `revoke`.
|
|
28
57
|
|
|
29
58
|
### Prefer npm
|
|
30
59
|
|
|
@@ -47,15 +76,7 @@ gipity update # force an immediate update now
|
|
|
47
76
|
|
|
48
77
|
To opt out: `export DISABLE_AUTOUPDATER=1` (matches Claude Code), or set `{ "autoUpdates": false }` in `~/.gipity/settings.json`. CI environments are auto-detected and skipped.
|
|
49
78
|
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
One command. It walks you through login, project setup, and drops you into Claude Code.
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
gipity claude --dangerously-skip-permissions
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
That's it. You'll see:
|
|
79
|
+
## What `gipity build` looks like
|
|
59
80
|
|
|
60
81
|
```
|
|
61
82
|
Welcome to Gipity
|
|
@@ -72,16 +93,29 @@ That's it. You'll see:
|
|
|
72
93
|
2. Create new project
|
|
73
94
|
|
|
74
95
|
Choose (1-2): 2
|
|
75
|
-
Project name [
|
|
96
|
+
Project name [project-001]: cool-app
|
|
76
97
|
Creating "cool-app"...
|
|
77
98
|
Created.
|
|
78
99
|
|
|
79
|
-
|
|
100
|
+
Which coding agent?
|
|
101
|
+
1. Claude Code (Anthropic)
|
|
102
|
+
2. Codex (OpenAI)
|
|
103
|
+
3. Grok (xAI)
|
|
104
|
+
|
|
105
|
+
Launching Claude Code, powered by Gipity.
|
|
80
106
|
```
|
|
81
107
|
|
|
82
|
-
If you're already logged in, it skips straight to project
|
|
108
|
+
If you're already logged in, it skips straight to the project picker. If you're already inside a Gipity project directory, it uses that project. Your last-used agent is the default next time.
|
|
109
|
+
|
|
110
|
+
Projects live in `~/GipityProjects/{project-slug}/` - created automatically on first use. Any extra flags (like `--model opus`) pass straight through to the agent. Useful flags:
|
|
83
111
|
|
|
84
|
-
|
|
112
|
+
```bash
|
|
113
|
+
gipity build --agent codex # Skip the agent picker
|
|
114
|
+
gipity build --new-project --name app # Create a fresh project without the picker
|
|
115
|
+
gipity build --project my-app # Open a specific project
|
|
116
|
+
gipity build --here # Use the current directory, not ~/GipityProjects/
|
|
117
|
+
gipity build -p "add a contact form" # Headless one-shot (no interactive session)
|
|
118
|
+
```
|
|
85
119
|
|
|
86
120
|
### The manual way
|
|
87
121
|
|
|
@@ -92,26 +126,28 @@ gipity login --email you@example.com
|
|
|
92
126
|
gipity login --code 123456
|
|
93
127
|
cd my-project
|
|
94
128
|
gipity init
|
|
95
|
-
claude
|
|
129
|
+
claude # or codex, or grok
|
|
96
130
|
```
|
|
97
131
|
|
|
98
|
-
##
|
|
132
|
+
## Coding Agent Integration
|
|
133
|
+
|
|
134
|
+
This is the good part. When you run `gipity init` (or `gipity build`) in a project, it wires two hooks into your agent (Claude Code, Codex, and Grok all get them):
|
|
99
135
|
|
|
100
|
-
|
|
136
|
+
**Auto-push** - Every time your agent writes or edits a file, it gets pushed to Gipity in the background. No extra steps.
|
|
101
137
|
|
|
102
|
-
**Auto-
|
|
138
|
+
**Auto-pull** - Before each turn, your agent pulls any changes that happened remotely (like if your Gipity agent built something via chat). It sees what changed and can pick up where things left off.
|
|
103
139
|
|
|
104
|
-
|
|
140
|
+
That means your local agent and your Gipity agent share the same files, same project, same context. You get the best of both - hands-on coding locally, autonomous agent work on Gipity.
|
|
105
141
|
|
|
106
|
-
|
|
142
|
+
Sessions are also recorded to your Gipity project so you can watch them live at prompt.gipity.ai. Opt out per project with `gipity init --no-capture`.
|
|
107
143
|
|
|
108
144
|
### What gets set up
|
|
109
145
|
|
|
110
146
|
```
|
|
111
147
|
.gipity.json # Project config (which project, which agent)
|
|
112
148
|
.gipity/ # Local sync state (gitignored)
|
|
113
|
-
.claude/settings.json # Hooks for auto-push and auto-pull
|
|
114
|
-
CLAUDE.md
|
|
149
|
+
.claude/settings.json # Hooks for auto-push and auto-pull (per-agent equivalents for Codex/Grok)
|
|
150
|
+
CLAUDE.md / AGENTS.md # Gipity commands reference for your agent
|
|
115
151
|
```
|
|
116
152
|
|
|
117
153
|
### Manual sync
|
|
@@ -128,9 +164,11 @@ gipity sync down # Pull remote changes
|
|
|
128
164
|
|
|
129
165
|
| Command | What it does |
|
|
130
166
|
|---------|-------------|
|
|
131
|
-
| `gipity
|
|
167
|
+
| `gipity build` | Log in, pick a project, pick your coding agent, and launch it - all in one |
|
|
168
|
+
| `gipity init` | Link this directory to a project and set up your coding agent |
|
|
169
|
+
| `gipity connect` | Connect this computer to gipity.ai so the web CLI can drive it |
|
|
170
|
+
| `gipity relay` | Manage the relay (status, pause, resume, revoke) |
|
|
132
171
|
| `gipity login` | Authenticate with email + verification code |
|
|
133
|
-
| `gipity init` | Set up a Gipity project and configure Claude Code |
|
|
134
172
|
| `gipity status` | Show project, agent, and auth info |
|
|
135
173
|
| `gipity sync` | Sync files between local and Gipity |
|
|
136
174
|
| `gipity push <file>` | Push a single file |
|
|
@@ -5,11 +5,6 @@ export const claudeCodeAdapter = {
|
|
|
5
5
|
displayName: 'Claude Code',
|
|
6
6
|
providerName: 'Anthropic',
|
|
7
7
|
binary: 'claude',
|
|
8
|
-
models: [
|
|
9
|
-
{ id: 'opus', label: 'Opus' },
|
|
10
|
-
{ id: 'sonnet', label: 'Sonnet' },
|
|
11
|
-
{ id: 'haiku', label: 'Haiku' },
|
|
12
|
-
],
|
|
13
8
|
buildInteractiveArgs({ resume, model }) {
|
|
14
9
|
const args = [];
|
|
15
10
|
if (model)
|
package/dist/agents/codex.js
CHANGED
|
@@ -4,10 +4,6 @@ export const codexAdapter = {
|
|
|
4
4
|
displayName: 'Codex',
|
|
5
5
|
providerName: 'OpenAI',
|
|
6
6
|
binary: 'codex',
|
|
7
|
-
models: [
|
|
8
|
-
{ id: 'gpt-5.1-codex', label: 'GPT-5.1 Codex' },
|
|
9
|
-
{ id: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' },
|
|
10
|
-
],
|
|
11
7
|
buildInteractiveArgs({ resume, model }) {
|
|
12
8
|
const args = [];
|
|
13
9
|
if (model)
|
package/dist/agents/grok.js
CHANGED
|
@@ -4,10 +4,6 @@ export const grokAdapter = {
|
|
|
4
4
|
displayName: 'Grok',
|
|
5
5
|
providerName: 'xAI',
|
|
6
6
|
binary: 'grok',
|
|
7
|
-
models: [
|
|
8
|
-
{ id: 'grok-4.5', label: 'Grok 4.5' },
|
|
9
|
-
{ id: 'grok-code', label: 'Grok Code' },
|
|
10
|
-
],
|
|
11
7
|
buildInteractiveArgs({ resume, model }) {
|
|
12
8
|
const args = [];
|
|
13
9
|
if (model)
|
package/dist/api.js
CHANGED
|
@@ -56,8 +56,8 @@ async function bearerToken() {
|
|
|
56
56
|
/** True when a long-lived agent API token (GIPITY_TOKEN) is in play. Such a token
|
|
57
57
|
* is static — it can't be refreshed — so the 401 self-heal path must skip it and
|
|
58
58
|
* the "run: gipity login" hint must not be shown (that's the wrong recovery for
|
|
59
|
-
* env-token callers). */
|
|
60
|
-
function usingEnvToken() {
|
|
59
|
+
* env-token callers). Exported so `gipity status` can name the auth source. */
|
|
60
|
+
export function usingEnvToken() {
|
|
61
61
|
return !!process.env.GIPITY_TOKEN?.trim();
|
|
62
62
|
}
|
|
63
63
|
// A transient 401 on a session token means the access token the server just saw
|
|
@@ -83,9 +83,12 @@ async function shouldRetryAfter401(status, retried) {
|
|
|
83
83
|
/** Append a re-login hint to a 401 message for session users. A 401 that
|
|
84
84
|
* survived the refresh-and-retry is an unrecoverable session (the refresh token
|
|
85
85
|
* is gone too); env-token callers authenticate differently, so don't misdirect
|
|
86
|
-
* them.
|
|
86
|
+
* them. Names the headless path too — an unattended agent has no mailbox for
|
|
87
|
+
* the emailed login code, and dead-ending there stranded whole runs (cli#137). */
|
|
87
88
|
function with401Hint(status, message) {
|
|
88
|
-
return status === 401 && !usingEnvToken()
|
|
89
|
+
return status === 401 && !usingEnvToken()
|
|
90
|
+
? `${message} — run: gipity login (headless/CI: set GIPITY_TOKEN instead — gipity skill read agent-deploy)`
|
|
91
|
+
: message;
|
|
89
92
|
}
|
|
90
93
|
async function getHeaders() {
|
|
91
94
|
return {
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `gipity add` catalog - template + kit keys with terse list hints.
|
|
3
|
+
*
|
|
4
|
+
* AUTO-GENERATED - do not edit directly.
|
|
5
|
+
* Source: platform/packages/shared/src/constants.ts (TEMPLATES/KITS cliHint fields)
|
|
6
|
+
* Run `just build-knowledge` to refresh.
|
|
7
|
+
*/
|
|
8
|
+
/** Visible starter templates (complete working demos). */
|
|
9
|
+
export const STARTERS = [
|
|
10
|
+
{ key: 'web-vision-cam', hint: 'fullscreen camera app with on-device vision (MediaPipe)' },
|
|
11
|
+
{ key: 'object-spotter', hint: 'camera app that boxes, labels, and counts objects (YOLOX on-device)' },
|
|
12
|
+
{ key: '2d-game', hint: '2D games with Phaser 3 - platformer, arcade, puzzle' },
|
|
13
|
+
{ key: '3d-world', hint: 'playable 3D multiplayer rocket-launcher demo' },
|
|
14
|
+
{ key: 'karaoke-captions', hint: 'audio + lyrics -> word-synced karaoke captions (GPU job)' },
|
|
15
|
+
{ key: 'outreach-agent', hint: 'AI-run outreach funnel - import contacts, draft + auto-send staged emails' },
|
|
16
|
+
{ key: 'paid-app', hint: 'storefront that charges real money - Stripe checkout, members area, billing' },
|
|
17
|
+
{ key: 'notify-demo', hint: 'web-push demo - enable notifications, send a real ping' },
|
|
18
|
+
];
|
|
19
|
+
/** Visible blank-wiring templates. */
|
|
20
|
+
export const BLANK = [
|
|
21
|
+
{ key: 'web-simple', hint: 'static frontend-only site - pages, dashboards, simple games' },
|
|
22
|
+
{ key: 'web-fullstack', hint: 'backend API + database wiring - frontend, functions, migrations; deploys green' },
|
|
23
|
+
{ key: '3d-engine', hint: '3D multiplayer wiring - Three.js + Rapier + Gipity Realtime' },
|
|
24
|
+
{ key: 'api', hint: 'pure API backend, no frontend - one example function + test' },
|
|
25
|
+
];
|
|
26
|
+
/** Hidden templates - installable by exact key, omitted from listings. */
|
|
27
|
+
export const HIDDEN = [
|
|
28
|
+
{ key: 'app-itsm', hint: 'IT service management / helpdesk / ticketing' },
|
|
29
|
+
{ key: 'monitor', hint: 'account observability dashboard (auto-installed per account)' },
|
|
30
|
+
];
|
|
31
|
+
/** Kits - building blocks added into an existing app. */
|
|
32
|
+
export const KITS = [
|
|
33
|
+
{ key: 'realtime', hint: 'multiplayer / presence / shared state' },
|
|
34
|
+
{ key: 'web-vision-mediapipe', hint: 'browser camera vision - gesture, pose, object detection' },
|
|
35
|
+
{ key: 'web-vision-detect', hint: 'browser object detection - YOLOX, WebGPU/WASM, custom models' },
|
|
36
|
+
{ key: 'chatbot', hint: 'drop-in chatbot - persona, guardrails, streaming responses' },
|
|
37
|
+
{ key: 'audio-align', hint: 'audio + lyrics -> word-level timing JSON (GPU job)' },
|
|
38
|
+
{ key: 'i18n', hint: 'multi-language web apps - language picker, RTL, translations' },
|
|
39
|
+
{ key: 'records', hint: 'registry-driven data plane - generic CRUD, validation, search, audit spine' },
|
|
40
|
+
{ key: 'views', hint: 'registry-driven UI over records - table, forms, kanban' },
|
|
41
|
+
{ key: 'agent-api', hint: 'named API keys for agent/script writes through the records kit' },
|
|
42
|
+
{ key: 'contacts', hint: 'multi-source contact layer - LinkedIn/Gmail import, dedupe with provenance' },
|
|
43
|
+
{ key: 'stripe', hint: 'charge your users - Stripe checkout, subscriptions, brokered webhooks' },
|
|
44
|
+
{ key: 'notify', hint: 'web push notifications - platform-owned keys, works on iOS home screen' },
|
|
45
|
+
];
|
|
46
|
+
//# sourceMappingURL=catalog.js.map
|
package/dist/commands/add.js
CHANGED
|
@@ -8,28 +8,15 @@ import { sync } from '../sync.js';
|
|
|
8
8
|
import { success, muted, bold, warning, error as clrError } from '../colors.js';
|
|
9
9
|
import { run } from '../helpers/index.js';
|
|
10
10
|
import { createProgressReporter, withSpinner } from '../progress.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{ key: 'web-fullstack', hint: 'backend API + database wiring - frontend, functions, migrations; deploys green' },
|
|
21
|
-
{ key: 'api', hint: 'pure API backend, no frontend - one example function + test' },
|
|
22
|
-
{ key: '3d-engine', hint: '3D multiplayer wiring - Three.js + Rapier + Gipity Realtime' },
|
|
23
|
-
];
|
|
24
|
-
const HIDDEN = [{ key: 'app-itsm', hint: 'IT service management / helpdesk / ticketing' }];
|
|
25
|
-
const KITS = [
|
|
26
|
-
{ key: 'realtime', hint: 'multiplayer / presence / shared state' },
|
|
27
|
-
{ key: 'web-vision-mediapipe', hint: 'browser camera vision - gesture, pose, object detection' },
|
|
28
|
-
{ key: 'web-vision-detect', hint: 'browser object detection - YOLOX, WebGPU/WASM, custom models' },
|
|
29
|
-
{ key: 'chatbot', hint: 'drop-in chatbot - persona, guardrails, streaming responses' },
|
|
30
|
-
{ key: 'audio-align', hint: 'audio + lyrics -> word-level timing JSON (GPU job)' },
|
|
31
|
-
{ key: 'i18n', hint: 'multi-language web apps - language picker, RTL, translations' },
|
|
32
|
-
];
|
|
11
|
+
// Catalog GENERATED from platform/packages/shared (TEMPLATES + KITS cliHint
|
|
12
|
+
// fields) into src/catalog.ts by platform/scripts/build-knowledge.ts - the CLI
|
|
13
|
+
// ships as a standalone npm package and can't depend on the private shared
|
|
14
|
+
// workspace. The hand-mirrored copy that used to live here silently dropped
|
|
15
|
+
// every new catalog entry; edit constants.ts + `just build-knowledge` instead.
|
|
16
|
+
//
|
|
17
|
+
// Templates install a whole app (blank wiring or a working starter demo).
|
|
18
|
+
// Kits are reusable building blocks added into an existing app's src/packages/.
|
|
19
|
+
import { STARTERS, BLANK, KITS } from '../catalog.js';
|
|
33
20
|
// The catalog block, rendered once and reused by the full help output
|
|
34
21
|
// (`gipity add` / `gipity add --help`) and the bare listing (`gipity add
|
|
35
22
|
// --list`) so they can never drift. Three sections, one entry per line, keys
|
package/dist/commands/build.js
CHANGED
|
@@ -233,9 +233,9 @@ function suggestProjectName(existingSlugs) {
|
|
|
233
233
|
return `project-${Date.now().toString(36).slice(-6)}`;
|
|
234
234
|
}
|
|
235
235
|
/** Both launch commands share one implementation: `gipity build` (the
|
|
236
|
-
* documented start-from-anywhere funnel: project picker + agent picker
|
|
237
|
-
*
|
|
238
|
-
*
|
|
236
|
+
* documented start-from-anywhere funnel: project picker + agent picker) and
|
|
237
|
+
* the hidden legacy `gipity claude` (identical behavior with the agent
|
|
238
|
+
* pinned to Claude and no agent question - kept
|
|
239
239
|
* working forever because deployed relay daemons and the GUI installer
|
|
240
240
|
* invoke it, but no longer advertised anywhere). */
|
|
241
241
|
function createLaunchCommand(name, cfg = {}) {
|
|
@@ -250,7 +250,7 @@ function createLaunchCommand(name, cfg = {}) {
|
|
|
250
250
|
// Forwarded to the agent via the unknown-arg passthrough below (NOT in the
|
|
251
251
|
// gipity strip lists). Declared here only so it shows up in --help — without
|
|
252
252
|
// this, callers can't discover that the session model is selectable.
|
|
253
|
-
.option('--model <model>', 'Model for the session (e.g. opus
|
|
253
|
+
.option('--model <model>', 'Model for the session, passed straight to the agent (e.g. opus) - omit it and the agent uses its own default')
|
|
254
254
|
.option('--bypass-approvals', 'Skip the agent\'s interactive tool approvals (headless runs; the relay daemon sets this)')
|
|
255
255
|
.allowUnknownOption(true)
|
|
256
256
|
.allowExcessArguments(true);
|
|
@@ -696,22 +696,18 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
696
696
|
}
|
|
697
697
|
}
|
|
698
698
|
const adapter = getAdapter(agentKey);
|
|
699
|
-
//
|
|
700
|
-
// the
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
//
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
let pickedModel = null;
|
|
707
|
-
if (!nonInteractive && !modelFlagGiven && !cmdCfg.presetAgent) {
|
|
708
|
-
pickedModel = await pickModel(adapter, prefs.lastModel?.[adapter.key] ?? null);
|
|
709
|
-
writePrefs({ lastAgent: adapter.key, lastModel: { [adapter.key]: pickedModel } });
|
|
699
|
+
// We never ask about the model: an explicit `--model` passes straight
|
|
700
|
+
// through to the agent, and with no flag the agent's own default (and
|
|
701
|
+
// its own model memory) stays authoritative. Only an interactive pick
|
|
702
|
+
// is remembered (a daemon dispatch or the legacy `gipity claude` shim
|
|
703
|
+
// must not clobber the user's agent memory).
|
|
704
|
+
if (!nonInteractive && !cmdCfg.presetAgent) {
|
|
705
|
+
writePrefs({ lastAgent: adapter.key });
|
|
710
706
|
}
|
|
711
707
|
// ── Step 4: Launch the agent ───────────────────────────────────────
|
|
712
708
|
if (adapter.key !== 'claude') {
|
|
713
709
|
await launchNonClaudeAgent(adapter, {
|
|
714
|
-
cmdName, opts, rawArgs, nonInteractive, headlessOut, runStart,
|
|
710
|
+
cmdName, opts, rawArgs, nonInteractive, headlessOut, runStart,
|
|
715
711
|
});
|
|
716
712
|
return;
|
|
717
713
|
}
|
|
@@ -873,12 +869,9 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
873
869
|
}
|
|
874
870
|
}
|
|
875
871
|
else {
|
|
872
|
+
// Any `--model` the user passed is already in claudeArgs (passthrough);
|
|
873
|
+
// with no flag Claude picks its own default.
|
|
876
874
|
allArgs = initialPrompt ? [initialPrompt, ...claudeArgs] : claudeArgs;
|
|
877
|
-
// Model from the interactive picker (an explicit --model flag skipped
|
|
878
|
-
// the picker and is already in claudeArgs; "Agent default" is null and
|
|
879
|
-
// deliberately passes nothing).
|
|
880
|
-
if (pickedModel)
|
|
881
|
-
allArgs.push('--model', pickedModel);
|
|
882
875
|
}
|
|
883
876
|
// Single-command headless flows (`--new-project` / `--project` with -p)
|
|
884
877
|
// have no human to approve tool use - auto-bypass so Claude can actually
|
|
@@ -957,10 +950,11 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
957
950
|
process.exit(1);
|
|
958
951
|
}
|
|
959
952
|
}
|
|
960
|
-
// ─── Agent
|
|
953
|
+
// ─── Agent picker ─────────────────────────────────────────────────────────
|
|
961
954
|
// Same pick-a-number-or-hit-enter format as the project picker. The
|
|
962
|
-
// enter-enter path must stay the visibly obvious one: default agent
|
|
963
|
-
// last-used (else Claude)
|
|
955
|
+
// enter-enter path must stay the visibly obvious one: the default agent is
|
|
956
|
+
// the last-used one (else Claude). We deliberately don't ask about the model
|
|
957
|
+
// - the agent's own default is always right and a curated list only rots.
|
|
964
958
|
async function pickAgent(lastUsed) {
|
|
965
959
|
const defaultKey = lastUsed && AGENT_KEYS.includes(lastUsed) ? lastUsed : 'claude';
|
|
966
960
|
const defaultIdx = AGENT_ADAPTERS.findIndex(a => a.key === defaultKey) + 1;
|
|
@@ -983,22 +977,6 @@ async function pickAgent(lastUsed) {
|
|
|
983
977
|
console.log('');
|
|
984
978
|
return AGENT_ADAPTERS[choice - 1].key;
|
|
985
979
|
}
|
|
986
|
-
/** Returns the model id to pass via --model, or null for "Agent default"
|
|
987
|
-
* (no flag at all - the agent's own model memory stays authoritative). */
|
|
988
|
-
async function pickModel(adapter, lastUsed) {
|
|
989
|
-
console.log(` ${bold('Which model?')}\n`);
|
|
990
|
-
const lastIsModel = lastUsed && adapter.models.some(m => m.id === lastUsed);
|
|
991
|
-
const defaultIdx = lastIsModel ? adapter.models.findIndex(m => m.id === lastUsed) + 2 : 1;
|
|
992
|
-
console.log(` ${bold('1.')} Agent default ${muted(defaultIdx === 1 ? '(recommended)' : '')}`);
|
|
993
|
-
adapter.models.forEach((m, i) => {
|
|
994
|
-
const note = m.id === lastUsed ? ` ${muted('(last used)')}` : '';
|
|
995
|
-
console.log(` ${bold(`${i + 2}.`)} ${m.label}${note}`);
|
|
996
|
-
});
|
|
997
|
-
console.log('');
|
|
998
|
-
const choice = await pickOne('Choose', adapter.models.length + 1, defaultIdx);
|
|
999
|
-
console.log('');
|
|
1000
|
-
return choice === 1 ? null : adapter.models[choice - 2].id;
|
|
1001
|
-
}
|
|
1002
980
|
// ─── Non-Claude agent launch (Codex, Grok) ────────────────────────────────
|
|
1003
981
|
// The Claude path above carries years of Claude-specific plumbing (plugin
|
|
1004
982
|
// install, folder trust, stream-json capture gating, -p context wrapping).
|
|
@@ -1008,7 +986,7 @@ async function pickModel(adapter, lastUsed) {
|
|
|
1008
986
|
// (there is no stream-json ingest path for them, so GIPITY_CAPTURE is never
|
|
1009
987
|
// turned off here).
|
|
1010
988
|
async function launchNonClaudeAgent(adapter, ctx) {
|
|
1011
|
-
const { opts, rawArgs, nonInteractive, headlessOut, runStart
|
|
989
|
+
const { opts, rawArgs, nonInteractive, headlessOut, runStart } = ctx;
|
|
1012
990
|
// ── Binary ──────────────────────────────────────────────────────────────
|
|
1013
991
|
if (!binaryOnPath(adapter.binary)) {
|
|
1014
992
|
let installed = false;
|
|
@@ -1030,7 +1008,7 @@ async function launchNonClaudeAgent(adapter, ctx) {
|
|
|
1030
1008
|
const valueFlags = ['--api-base', '--name', '--project', '--agent'];
|
|
1031
1009
|
let message = null;
|
|
1032
1010
|
let resume;
|
|
1033
|
-
let model
|
|
1011
|
+
let model;
|
|
1034
1012
|
const extras = [];
|
|
1035
1013
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
1036
1014
|
const arg = rawArgs[i];
|
package/dist/commands/db.js
CHANGED
|
@@ -44,6 +44,11 @@ dbCommand
|
|
|
44
44
|
console.log(columns.map(c => String(row[c] ?? 'NULL')).join('\t'));
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
// DML with RETURNING carries both rows and a count - the rows are the
|
|
48
|
+
// table above; still name how many rows the statement touched.
|
|
49
|
+
if (res.data.affectedRows !== undefined) {
|
|
50
|
+
console.log(`Affected rows: ${res.data.affectedRows}`);
|
|
51
|
+
}
|
|
47
52
|
}
|
|
48
53
|
else if (res.data.affectedRows !== undefined) {
|
|
49
54
|
console.log(`Affected rows: ${res.data.affectedRows}`);
|
|
@@ -51,6 +56,11 @@ dbCommand
|
|
|
51
56
|
else if (res.data.results) {
|
|
52
57
|
console.log(JSON.stringify(res.data.results, null, 2));
|
|
53
58
|
}
|
|
59
|
+
else {
|
|
60
|
+
// Unknown payload shape: print it raw rather than nothing - silence
|
|
61
|
+
// here reads as "the query returned no data", which is a lie.
|
|
62
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
63
|
+
}
|
|
54
64
|
}
|
|
55
65
|
}));
|
|
56
66
|
dbCommand
|
package/dist/commands/deploy.js
CHANGED
|
@@ -146,6 +146,13 @@ export const deployCommand = new Command('deploy')
|
|
|
146
146
|
// has to reconstruct the URL convention or guess a subdomain.
|
|
147
147
|
if (d.url)
|
|
148
148
|
console.log(`${muted('Live:')} ${brand(d.url)}`);
|
|
149
|
+
// Functions are served from the API host, NOT the static Live URL -
|
|
150
|
+
// for an API-only app the endpoint address IS the deliverable, so name
|
|
151
|
+
// it instead of leaving it to be inferred from a template comment.
|
|
152
|
+
if (d.phases?.some(p => p.name === 'functions')) {
|
|
153
|
+
console.log(`${muted('Functions:')} POST ${brand(`${config.apiBase}/api/${config.projectGuid}/fn/<name>`)}`);
|
|
154
|
+
console.log(muted(' (same address on dev and prod; names: gipity fn list; verify the public path: gipity fn call <name> --anon)'));
|
|
155
|
+
}
|
|
149
156
|
await inspectAfter();
|
|
150
157
|
}
|
|
151
158
|
}));
|
package/dist/commands/fn.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { get, post, del, publicPost } from '../api.js';
|
|
3
|
+
import { getAuth } from '../auth.js';
|
|
3
4
|
import { requireConfig } from '../config.js';
|
|
4
5
|
import { error as clrError, bold, muted, success, warning } from '../colors.js';
|
|
5
6
|
import { run, printList, emitField } from '../helpers/index.js';
|
|
@@ -17,6 +18,11 @@ fnCommand
|
|
|
17
18
|
const line = `${bold(f.name)} ${muted(`v${f.version}`)} ${muted(f.auth_level)} ${muted(`timeout=${f.timeout_ms}ms`)}`;
|
|
18
19
|
return f.description ? `${line}\n${muted(f.description)}` : line;
|
|
19
20
|
});
|
|
21
|
+
// The callable address is the deliverable for API-only apps - name it here
|
|
22
|
+
// rather than leaving it to be inferred from a template comment.
|
|
23
|
+
if (!opts.json && res.data.length > 0) {
|
|
24
|
+
console.log(muted(`Endpoint: POST ${config.apiBase}/api/${config.projectGuid}/fn/<name> (same on dev and prod; public path: gipity fn call <name> --anon)`));
|
|
25
|
+
}
|
|
20
26
|
}));
|
|
21
27
|
fnCommand
|
|
22
28
|
.command('logs <name>')
|
|
@@ -29,8 +35,15 @@ fnCommand
|
|
|
29
35
|
printList(res.data, opts, 'No execution logs.', log => {
|
|
30
36
|
const dur = log.duration_ms != null ? `${log.duration_ms}ms` : '?';
|
|
31
37
|
const ts = new Date(log.created_at).toLocaleString();
|
|
32
|
-
|
|
38
|
+
// The runtime writes 'ok' | 'error' | 'limit_exceeded' - never 'success'
|
|
39
|
+
// (same dead value already fixed in `logs fn`).
|
|
40
|
+
const statusColor = log.status === 'ok' ? success : log.status === 'error' ? clrError : muted;
|
|
33
41
|
let line = `${statusColor(log.status)} ${dur} ${muted(log.trigger_type || 'http')} ${muted(ts)}`;
|
|
42
|
+
// Platform service calls made inside the invocation (notify, email,
|
|
43
|
+
// LLM, ...) - per-call outcomes live in `gipity logs app --type services`.
|
|
44
|
+
const svcCalls = log.limits_consumed?.max_service_calls;
|
|
45
|
+
if (svcCalls)
|
|
46
|
+
line += ` ${muted(`svc:${svcCalls}`)}`;
|
|
34
47
|
if (log.error_message)
|
|
35
48
|
line += `\n${clrError(`error: ${log.error_message}`)}`;
|
|
36
49
|
// Captured console.log/warn/error output for this invocation.
|
|
@@ -69,6 +82,16 @@ fnCommand
|
|
|
69
82
|
const raw = bodyArg || opts.data || '{}';
|
|
70
83
|
const body = JSON.parse(raw);
|
|
71
84
|
const path = `/api/${config.projectGuid}/fn/${encodeURIComponent(name)}`;
|
|
85
|
+
// Name the calling identity (stderr, so stdout stays parseable). Without
|
|
86
|
+
// this, a "test the anonymous path" call silently runs authenticated as
|
|
87
|
+
// the signed-in owner and the public path never gets exercised.
|
|
88
|
+
if (opts.anon) {
|
|
89
|
+
console.error(muted('Auth: anonymous visitor (the public path a signed-out user hits)'));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const who = getAuth()?.email;
|
|
93
|
+
console.error(muted(`Auth: calling as ${who ?? 'your signed-in account'} (the owner persona; use --anon for the public visitor path)`));
|
|
94
|
+
}
|
|
72
95
|
const res = opts.anon
|
|
73
96
|
? await callAnon(config.projectGuid, name, body)
|
|
74
97
|
: await post(path, body);
|
package/dist/commands/logs.js
CHANGED
|
@@ -9,7 +9,20 @@ logsCommand
|
|
|
9
9
|
.command('fn <name>')
|
|
10
10
|
.description('Show function logs')
|
|
11
11
|
.option('--limit <n>', 'Max entries', '20')
|
|
12
|
-
.option('--json', 'Output as JSON')
|
|
12
|
+
.option('--json', 'Output as JSON (top-level ARRAY of invocations, newest first)')
|
|
13
|
+
.addHelpText('after', `
|
|
14
|
+
Each line: time, status (ok / error / limit_exceeded), duration, trigger, and
|
|
15
|
+
svc:N when the invocation made N platform service calls (notify, email, LLM,
|
|
16
|
+
image, ...). Captured console output is indented beneath its invocation.
|
|
17
|
+
|
|
18
|
+
--json emits a top-level ARRAY (not an object) of invocation rows:
|
|
19
|
+
[{ id, status, duration_ms, trigger_type, error_message,
|
|
20
|
+
limits_consumed: { max_queries, max_service_calls, ... }, logs: [...], created_at }]
|
|
21
|
+
|
|
22
|
+
To confirm an individual service call succeeded (and see provider, latency,
|
|
23
|
+
and its own error message), use the per-call log instead:
|
|
24
|
+
$ gipity logs app --type services
|
|
25
|
+
`)
|
|
13
26
|
.action((name, opts) => run('Logs', async () => {
|
|
14
27
|
const config = requireConfig();
|
|
15
28
|
const limit = parseInt(opts.limit, 10) || 20;
|
|
@@ -32,8 +45,14 @@ logsCommand
|
|
|
32
45
|
const statusColor = log.status === 'ok' ? success : log.status === 'error' ? clrError : warning;
|
|
33
46
|
const status = statusColor(log.status.padEnd(8));
|
|
34
47
|
const trigger = muted(log.trigger_type.padEnd(8));
|
|
48
|
+
// Service calls made inside the invocation (notify, email, LLM, ...) are
|
|
49
|
+
// otherwise invisible here - surface the count so "did my send happen?"
|
|
50
|
+
// doesn't require spelunking --json limits counters. Per-call outcomes
|
|
51
|
+
// live in `gipity logs app --type services`.
|
|
52
|
+
const svcCalls = log.limits_consumed?.max_service_calls;
|
|
53
|
+
const svc = svcCalls ? ` ${muted(`svc:${svcCalls}`)}` : '';
|
|
35
54
|
const err = log.error_message ? ` ${clrError(`"${log.error_message}"`)}` : '';
|
|
36
|
-
console.log(`${muted(time)} ${status} ${dur} ${trigger}${err}`);
|
|
55
|
+
console.log(`${muted(time)} ${status} ${dur} ${trigger}${svc}${err}`);
|
|
37
56
|
// Captured console.log/warn/error output for this invocation, indented
|
|
38
57
|
// under its summary line. Empty for calls that printed nothing.
|
|
39
58
|
for (const line of log.logs ?? []) {
|
|
@@ -274,7 +274,8 @@ export function slowRenderMessage(fps, o) {
|
|
|
274
274
|
return `${warning('⚠ Slow render:')} page painted at ${fps} fps. `
|
|
275
275
|
+ `Waiting on real time (setTimeout) advances animation/physics time far slower than it looks — `
|
|
276
276
|
+ `assertions after a wall-clock wait can report a false negative. `
|
|
277
|
-
+ `Step the app's own loop deterministically instead (3D templates: ${bold('core.advance(seconds)')}
|
|
277
|
+
+ `Step the app's own loop deterministically instead (3D templates: ${bold('core.advance(seconds)')}; `
|
|
278
|
+
+ `2D/Phaser template: ${bold("(await import('./js/config.js')).advance(seconds)")}).`;
|
|
278
279
|
}
|
|
279
280
|
/** Poll the async eval job until it finishes. Eval runs server-side as a
|
|
280
281
|
* short-lived job (so a long --wait can't trip the gateway idle timeout);
|
|
@@ -570,7 +571,10 @@ export const pageEvalCommand = new Command('eval')
|
|
|
570
571
|
}));
|
|
571
572
|
return;
|
|
572
573
|
}
|
|
573
|
-
|
|
574
|
+
// Context echo (which URL, which script) goes to stderr: the caller
|
|
575
|
+
// already knows what it sent, and keeping stdout = qualifiers + result
|
|
576
|
+
// means `2>/dev/null` (or any stdout capture) yields just the answer.
|
|
577
|
+
console.error(`${brand('Eval')} ${bold(d.url || url)}`);
|
|
574
578
|
if (d.navigationIncomplete) {
|
|
575
579
|
console.log(`${warning('⚠ Navigation incomplete:')} ${d.note || 'page did not reach full load'}`);
|
|
576
580
|
}
|
|
@@ -597,7 +601,7 @@ export const pageEvalCommand = new Command('eval')
|
|
|
597
601
|
console.log(`${muted('Camera:')} ${camera.name} ${muted('(played as the webcam feed; getUserMedia resolves)')}`);
|
|
598
602
|
if (hosted.length)
|
|
599
603
|
console.log(`${muted('Fixtures:')} ${hosted.map((h) => h.name).join(', ')}`);
|
|
600
|
-
console.
|
|
604
|
+
console.error(opts.file ? `${muted('Script:')} ${opts.file}` : `${muted('Expression:')} ${summarizeExpr(expr)}`);
|
|
601
605
|
console.log(`\n${result.trim() ? result : muted('(empty result)')}`);
|
|
602
606
|
if (noValue)
|
|
603
607
|
console.log(muted(`\n${EVAL_NO_VALUE_HINT}`));
|
package/dist/commands/relay.js
CHANGED
|
@@ -29,7 +29,7 @@ relayCommand
|
|
|
29
29
|
.option('--name <name>', 'Device name shown in the web CLI (default: this machine\'s hostname)')
|
|
30
30
|
.option('--no-start', 'Pair only; do not start the relay daemon now')
|
|
31
31
|
.option('--no-autostart', 'Skip the OS login service (use when a supervising app owns the daemon)')
|
|
32
|
-
.option('--force', 'Re-pair even if already paired (
|
|
32
|
+
.option('--force', 'Re-pair even if already paired (re-registers this machine, rotating its token)')
|
|
33
33
|
.option('--json', 'Machine-readable output')
|
|
34
34
|
.action(async (opts) => {
|
|
35
35
|
const fail = (code, message) => {
|