gipity 1.0.408 → 1.0.409

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.
@@ -1,215 +0,0 @@
1
- /**
2
- * Hidden subcommand invoked by Claude Code hooks (see setup.ts HOOKS_SETTINGS).
3
- * Reads Claude's hook JSON on stdin and POSTs a capture event to Gipity.
4
- *
5
- * The target conversation is identified by the `GIPITY_CONVERSATION_GUID`
6
- * env var, which `gipity claude` exports before spawning Claude Code.
7
- * Every capture event is tagged with that conv_guid — no server-side
8
- * placeholder adoption, no guessing.
9
- *
10
- * Must never fail loudly — a hook that errors would degrade Claude Code's
11
- * UX. All errors are swallowed (optionally logged to
12
- * .gipity/hook-capture.log for debugging).
13
- */
14
- import { Command } from 'commander';
15
- import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync, appendFileSync } from 'fs';
16
- import { resolve, dirname } from 'path';
17
- import { getConfig } from '../config.js';
18
- import { getAuth } from '../auth.js';
19
- import { post } from '../api.js';
20
- function readStdin() {
21
- return new Promise((r) => {
22
- let data = '';
23
- process.stdin.setEncoding('utf-8');
24
- process.stdin.on('data', (c) => (data += c));
25
- process.stdin.on('end', () => r(data));
26
- process.stdin.on('error', () => r(data));
27
- });
28
- }
29
- function debugLog(msg) {
30
- if (!process.env.GIPITY_HOOK_DEBUG)
31
- return;
32
- try {
33
- const cfg = getConfig();
34
- if (!cfg)
35
- return;
36
- const logPath = resolve(process.cwd(), '.gipity', 'hook-capture.log');
37
- mkdirSync(dirname(logPath), { recursive: true });
38
- appendFileSync(logPath, `${new Date().toISOString()} ${msg}\n`);
39
- }
40
- catch { /* ignore */ }
41
- }
42
- const TTY = !!process.stderr.isTTY;
43
- const dim = (s) => TTY ? `\x1b[2m${s}\x1b[0m` : s;
44
- const cyan = (s) => TTY ? `\x1b[36m${s}\x1b[0m` : s;
45
- const red = (s) => TTY ? `\x1b[31m${s}\x1b[0m` : s;
46
- function hhmmss() {
47
- const d = new Date();
48
- const p = (n) => String(n).padStart(2, '0');
49
- return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
50
- }
51
- function friendlyLabel(path) {
52
- const m = path.match(/\/remote-sessions\/[^/]+\/(.+)$/);
53
- return m ? m[1] : path;
54
- }
55
- function summarize(body) {
56
- if (!body || typeof body !== 'object')
57
- return '';
58
- const b = body;
59
- if (typeof b.prompt === 'string') {
60
- const p = b.prompt.replace(/\s+/g, ' ').trim();
61
- return p.length > 80 ? `"${p.slice(0, 77)}…"` : `"${p}"`;
62
- }
63
- if (typeof b.tool_name === 'string')
64
- return b.tool_name;
65
- if (Array.isArray(b.entries))
66
- return `${b.entries.length} transcript entr${b.entries.length === 1 ? 'y' : 'ies'}`;
67
- if (typeof b.trigger === 'string')
68
- return b.trigger;
69
- if (typeof b.source === 'string')
70
- return b.source;
71
- return '';
72
- }
73
- async function safePost(path, body) {
74
- const label = friendlyLabel(path);
75
- const detail = summarize(body);
76
- process.stderr.write(`${dim(hhmmss())} ${cyan('↗ gipity')} ${label}${detail ? ' ' + dim(detail) : ''}\n`);
77
- try {
78
- await post(path, body);
79
- }
80
- catch (err) {
81
- process.stderr.write(`${dim(hhmmss())} ${red('✗ gipity')} ${label} ${dim(err?.message || String(err))}\n`);
82
- debugLog(`POST ${path} failed: ${err?.message || err}`);
83
- }
84
- }
85
- function offsetPath(sessionId) {
86
- return resolve(process.cwd(), '.gipity', 'transcripts', `${sessionId}.offset`);
87
- }
88
- function readOffset(sessionId) {
89
- try {
90
- return parseInt(readFileSync(offsetPath(sessionId), 'utf-8'), 10) || 0;
91
- }
92
- catch {
93
- return 0;
94
- }
95
- }
96
- function writeOffset(sessionId, offset) {
97
- const p = offsetPath(sessionId);
98
- mkdirSync(dirname(p), { recursive: true });
99
- writeFileSync(p, String(offset));
100
- }
101
- /** Parse JSONL delta from transcript_path starting at stored offset. */
102
- function readTranscriptDelta(transcriptPath, sessionId) {
103
- if (!existsSync(transcriptPath))
104
- return { entries: [], newOffset: 0 };
105
- const size = statSync(transcriptPath).size;
106
- let offset = readOffset(sessionId);
107
- if (offset > size)
108
- offset = 0; // regressed — rescan
109
- if (offset === size)
110
- return { entries: [], newOffset: offset };
111
- const fd = readFileSync(transcriptPath);
112
- const slice = fd.slice(offset).toString('utf-8');
113
- const lines = slice.split('\n').filter((l) => l.trim());
114
- const entries = [];
115
- for (const line of lines) {
116
- try {
117
- entries.push(JSON.parse(line));
118
- }
119
- catch { /* skip partial */ }
120
- }
121
- return { entries, newOffset: size };
122
- }
123
- /** Preflight: hooks are no-ops when we're not in a Gipity project, not
124
- * authed, or (crucially) when the parent `gipity claude` never exported
125
- * a conv_guid (unpaired device, failed create, etc.). Without the
126
- * conv_guid there is no conversation to attach events to. */
127
- function preflight() {
128
- if (!getConfig())
129
- return null;
130
- if (!getAuth())
131
- return null;
132
- const convGuid = process.env.GIPITY_CONVERSATION_GUID;
133
- if (!convGuid)
134
- return null;
135
- return { convGuid };
136
- }
137
- async function handleStart(payload, convGuid) {
138
- const { session_id, cwd, source } = payload;
139
- if (!session_id)
140
- return;
141
- // Attach this Claude Code run's session_id to the conv. Idempotent
142
- // server-side; harmless to call multiple times (e.g. after --resume).
143
- await safePost(`/remote-sessions/${encodeURIComponent(convGuid)}/attach-session`, {
144
- session_id,
145
- cwd: cwd || process.cwd(),
146
- source: source || 'startup',
147
- });
148
- }
149
- async function handlePrompt(payload, convGuid) {
150
- const { prompt } = payload;
151
- await safePost(`/remote-sessions/${encodeURIComponent(convGuid)}/prompt`, {
152
- prompt: prompt || '',
153
- ts: new Date().toISOString(),
154
- });
155
- }
156
- async function handleTool(payload, convGuid) {
157
- const { tool_name, tool_input, tool_response } = payload;
158
- if (!tool_name)
159
- return;
160
- await safePost(`/remote-sessions/${encodeURIComponent(convGuid)}/tool`, {
161
- tool_name,
162
- tool_input: tool_input ?? null,
163
- tool_response: tool_response ?? null,
164
- });
165
- }
166
- async function handleStop(payload, convGuid) {
167
- const { session_id, transcript_path } = payload;
168
- if (!session_id || !transcript_path)
169
- return;
170
- const { entries, newOffset } = readTranscriptDelta(transcript_path, session_id);
171
- if (entries.length === 0)
172
- return;
173
- await safePost(`/remote-sessions/${encodeURIComponent(convGuid)}/transcript`, { entries });
174
- writeOffset(session_id, newOffset);
175
- }
176
- async function handleEnd(_payload, convGuid) {
177
- await safePost(`/remote-sessions/${encodeURIComponent(convGuid)}/end`, {});
178
- }
179
- async function handleCompact(payload, convGuid) {
180
- const { trigger } = payload;
181
- await safePost(`/remote-sessions/${encodeURIComponent(convGuid)}/compact`, {
182
- trigger: trigger || 'auto',
183
- });
184
- }
185
- async function run(event) {
186
- const pre = preflight();
187
- if (!pre)
188
- return;
189
- const raw = await readStdin();
190
- let payload;
191
- try {
192
- payload = JSON.parse(raw);
193
- }
194
- catch {
195
- debugLog(`bad JSON on ${event}`);
196
- return;
197
- }
198
- switch (event) {
199
- case 'start': return handleStart(payload, pre.convGuid);
200
- case 'prompt': return handlePrompt(payload, pre.convGuid);
201
- case 'tool': return handleTool(payload, pre.convGuid);
202
- case 'stop': return handleStop(payload, pre.convGuid);
203
- case 'end': return handleEnd(payload, pre.convGuid);
204
- case 'compact': return handleCompact(payload, pre.convGuid);
205
- }
206
- }
207
- export const hookCaptureCommand = new Command('hook-capture')
208
- .description('Internal: capture Claude Code hook events and forward to Gipity')
209
- .argument('<event>', 'hook event: start|prompt|tool|stop|end|compact')
210
- .action(async (event) => {
211
- await run(event);
212
- // Always succeed quietly — hooks must not surface errors to Claude Code.
213
- process.exit(0);
214
- });
215
- //# sourceMappingURL=hook-capture.js.map
@@ -1,58 +0,0 @@
1
- import { Command } from 'commander';
2
- import { post } from '../api.js';
3
- import { requireConfig } from '../config.js';
4
- import { sync } from '../sync.js';
5
- import { success } from '../colors.js';
6
- import { run } from '../helpers/index.js';
7
- // Scaffold types grouped by kind. Mirrors SCAFFOLD_TEMPLATES in
8
- // platform/packages/shared - kept inline here because the CLI is published
9
- // as a standalone npm package and can't depend on the private shared
10
- // workspace. When adding/removing/promoting an entry, update both lists.
11
- //
12
- // Templates are minimal wiring (start fresh). Starter apps are working demos.
13
- // Hidden entries are accepted by the server but not suggested by default -
14
- // they're shown to the agent in a "do-not-suggest-unsolicited" line so the
15
- // LLM can still pick one when the user asks for that domain by name.
16
- const VISIBLE_TEMPLATES = ['web-simple', '3d-engine'];
17
- const VISIBLE_STARTERS = ['web-fullstack', '2d-game', '3d-world', 'api'];
18
- const HIDDEN_STARTERS = [
19
- { key: 'app-itsm', hint: 'IT service management / helpdesk / ticketing / incident management' },
20
- ];
21
- const visibleTypeList = [
22
- `templates: ${VISIBLE_TEMPLATES.join(', ')}`,
23
- `starters: ${VISIBLE_STARTERS.join(', ')}`,
24
- ].join(' | ');
25
- const hiddenTypeList = HIDDEN_STARTERS.length
26
- ? ' | hidden (do NOT suggest unsolicited; use only when the user explicitly asks for that domain): ' +
27
- HIDDEN_STARTERS.map(h => `${h.key} (${h.hint})`).join(', ')
28
- : '';
29
- export const scaffoldCommand = new Command('scaffold')
30
- .description('Create an app from a template')
31
- .argument('[title]', 'App title (defaults to project name)')
32
- .requiredOption('--type <type>', `Project type: ${visibleTypeList}${hiddenTypeList}`)
33
- .option('--description <desc>', 'App description for meta tags')
34
- .option('--json', 'Output as JSON')
35
- .action((title, opts) => run('Scaffold', async () => {
36
- const config = requireConfig();
37
- const appTitle = title || config.projectSlug;
38
- const res = await post(`/projects/${config.projectGuid}/scaffold`, {
39
- title: appTitle,
40
- description: opts.description,
41
- type: opts.type,
42
- });
43
- // Sync down the created files
44
- const syncResult = await sync({ interactive: false });
45
- if (opts.json) {
46
- console.log(JSON.stringify({ ...res.data, synced: syncResult.applied }));
47
- }
48
- else {
49
- console.log(success(`Scaffolded "${res.data.title}" with ${res.data.files.length} files:`));
50
- for (const f of res.data.files) {
51
- console.log(` ${f}`);
52
- }
53
- if (syncResult.applied > 0) {
54
- console.log(`\nPulled ${syncResult.applied} files to local.`);
55
- }
56
- }
57
- }));
58
- //# sourceMappingURL=scaffold.js.map
@@ -1,45 +0,0 @@
1
- import { Command } from 'commander';
2
- import { get } from '../api.js';
3
- import { resolveProjectContext } from '../config.js';
4
- import { error as clrError, bold, muted } from '../colors.js';
5
- import { run, printList } from '../helpers/index.js';
6
- export const skillsCommand = new Command('skills')
7
- .description('Read platform docs');
8
- skillsCommand
9
- .command('list')
10
- .description('List skills')
11
- .option('--json', 'Output as JSON')
12
- .action((opts) => run('List', async () => {
13
- const { config } = await resolveProjectContext();
14
- if (!config.agentGuid) {
15
- console.error(clrError('No agent configured for this project. Run `gipity init` to refresh.'));
16
- process.exit(1);
17
- }
18
- const res = await get(`/skills?agent=${config.agentGuid}`);
19
- printList(res.data, opts, 'No skills available.', s => `${bold(s.name)} ${muted(s.description)}`);
20
- }));
21
- skillsCommand
22
- .command('read <name>')
23
- .description('Read a skill')
24
- .option('--json', 'Output as JSON')
25
- .action((name, opts) => run('Read', async () => {
26
- const { config } = await resolveProjectContext();
27
- if (!config.agentGuid) {
28
- console.error(clrError('No agent configured for this project. Run `gipity init` to refresh.'));
29
- process.exit(1);
30
- }
31
- const listRes = await get(`/skills?agent=${config.agentGuid}`);
32
- const match = listRes.data.find(s => s.name.toLowerCase() === name.toLowerCase());
33
- if (!match) {
34
- console.error(clrError(`Skill "${name}" not found. Run: gipity skills list`));
35
- process.exit(1);
36
- }
37
- const res = await get(`/skills/${match.guid}?agent=${config.agentGuid}`);
38
- if (opts.json) {
39
- console.log(JSON.stringify(res.data, null, 2));
40
- }
41
- else {
42
- console.log(res.data.content);
43
- }
44
- }));
45
- //# sourceMappingURL=skills.js.map
@@ -1,313 +0,0 @@
1
- import { Command } from 'commander';
2
- import { join } from 'path';
3
- import { mkdirSync } from 'fs';
4
- import { execSync, spawn } from 'child_process';
5
- import { homedir } from 'os';
6
- /** On Windows, spawn without shell:true needs an explicit extension (.exe or .cmd) */
7
- function resolveCommand(cmd) {
8
- if (process.platform !== 'win32')
9
- return cmd;
10
- try {
11
- const lines = execSync(`where ${cmd}`, { encoding: 'utf-8' }).split(/\r?\n/).map(l => l.trim()).filter(Boolean);
12
- // Prefer .exe (native) over .cmd (npm shim)
13
- return lines.find(l => l.endsWith('.exe')) || lines.find(l => l.endsWith('.cmd')) || cmd;
14
- }
15
- catch {
16
- return `${cmd}.cmd`;
17
- }
18
- }
19
- import { getAuth, saveAuth } from '../auth.js';
20
- import { get, post, publicPost } from '../api.js';
21
- import { getConfig, saveConfig, clearConfigCache, getApiBaseOverride } from '../config.js';
22
- import { syncDown, syncUp } from '../sync.js';
23
- import { slugify, setupClaudeHooks, setupClaudeMd, setupGitignore } from '../setup.js';
24
- import { prompt, pickOne, decodeJwtExp } from '../utils.js';
25
- import { brand, bold, faint, info, success, error as clrError, muted } from '../colors.js';
26
- const PROJECTS_ROOT = join(homedir(), 'GipityProjects');
27
- function suggestProjectName(existingSlugs) {
28
- const slugSet = new Set(existingSlugs);
29
- for (let i = 1; i <= 99; i++) {
30
- const candidate = `project${String(i).padStart(2, '0')}`;
31
- if (!slugSet.has(candidate))
32
- return candidate;
33
- }
34
- return `project-${Date.now().toString(36).slice(-6)}`;
35
- }
36
- export const startCcCommand = new Command('start-cc')
37
- .description('Log in, set up a project, and launch Claude Code')
38
- .option('--no-claude', 'Set up project but skip launching Claude Code')
39
- .allowUnknownOption(true)
40
- .allowExcessArguments(true)
41
- .action(async (opts) => {
42
- try {
43
- console.log(`\n ${brand(bold('Welcome to Gipity'))}`);
44
- console.log(` ${faint('─────────────────')}`);
45
- console.log('');
46
- // ── Step 1: Auth ──────────────────────────────────────────────────
47
- let auth = getAuth();
48
- if (auth) {
49
- console.log(` Logged in as ${auth.email}`);
50
- }
51
- else {
52
- console.log(' Let\'s get you logged in.\n');
53
- const email = await prompt(' Email: ');
54
- if (!email) {
55
- console.error(`\n ${clrError('Email required.')}`);
56
- process.exit(1);
57
- }
58
- await publicPost('/auth/login', { email });
59
- console.log(' Check your email for a 6-digit code.\n');
60
- const code = await prompt(' Code: ');
61
- if (!code) {
62
- console.error(`\n ${clrError('Code required.')}`);
63
- process.exit(1);
64
- }
65
- const res = await publicPost('/auth/verify', { email, code });
66
- const exp = decodeJwtExp(res.accessToken);
67
- if (!exp) {
68
- console.error(`\n ${clrError('Invalid token received.')}`);
69
- process.exit(1);
70
- }
71
- const expiresAt = new Date(exp * 1000).toISOString();
72
- saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
73
- auth = getAuth();
74
- console.log(` ${success(`Logged in as ${email}`)}`);
75
- }
76
- console.log('');
77
- // ── Step 2: Project ───────────────────────────────────────────────
78
- let initialPrompt = '';
79
- // If cwd already has .gipity.json, use it (user ran from inside a project)
80
- const existing = getConfig();
81
- if (existing) {
82
- console.log(` Project: ${brand(existing.projectSlug)} ${muted(`(${existing.projectGuid})`)}`);
83
- console.log(` ${success('Already set up.')}\n`);
84
- setupClaudeHooks();
85
- setupClaudeMd();
86
- setupGitignore();
87
- }
88
- else {
89
- // Fetch user's projects
90
- let projects = [];
91
- let fetchFailed = false;
92
- try {
93
- const res = await get('/projects?limit=100');
94
- projects = res.data;
95
- }
96
- catch (err) {
97
- fetchFailed = true;
98
- const isConnectionError = err?.code === 'ECONNREFUSED' || err?.code === 'ENOTFOUND' ||
99
- err?.code === 'ETIMEDOUT' || err?.cause?.code === 'ECONNREFUSED' ||
100
- err?.cause?.code === 'ENOTFOUND' || err?.cause?.code === 'ETIMEDOUT';
101
- if (isConnectionError) {
102
- const apiBase = getApiBaseOverride() || 'https://a.gipity.ai';
103
- console.error(` ${clrError(`Could not connect to ${apiBase}`)}`);
104
- console.error(` ${muted('Check your connection and try again.')}`);
105
- process.exit(1);
106
- }
107
- // Non-connection error (e.g. 401) — fall through to create
108
- }
109
- const existingSlugs = projects.map(p => p.slug);
110
- let project;
111
- let isNewProject = false;
112
- if (projects.length > 0) {
113
- const result = await pickOrCreateProject(projects, existingSlugs);
114
- project = result.project;
115
- isNewProject = result.isNew;
116
- }
117
- else if (!fetchFailed) {
118
- project = await createNewProject(existingSlugs);
119
- isNewProject = true;
120
- }
121
- else {
122
- console.error(` ${clrError('Could not load projects. Please try again.')}`);
123
- process.exit(1);
124
- }
125
- // Resolve project directory under ~/GipityProjects/{slug}
126
- const projectDir = join(PROJECTS_ROOT, project.slug);
127
- mkdirSync(projectDir, { recursive: true });
128
- process.chdir(projectDir);
129
- clearConfigCache();
130
- // Fetch agents
131
- let agentGuid = '';
132
- try {
133
- const agents = await get(`/projects/${project.short_guid}/agents`);
134
- if (agents.data.length > 0)
135
- agentGuid = agents.data[0].short_guid;
136
- }
137
- catch {
138
- // No agents
139
- }
140
- const accountSlug = project.user?.account_slug || '';
141
- // Always write config (refresh stale GUIDs from a previous setup)
142
- saveConfig({
143
- projectGuid: project.short_guid,
144
- projectSlug: project.slug,
145
- accountSlug,
146
- agentGuid,
147
- conversationGuid: null,
148
- apiBase: getApiBaseOverride() || 'https://a.gipity.ai',
149
- ignore: ['node_modules', '.git', '.gipity.json', '.gipity/', '.claude/', '.gitignore', 'CLAUDE.md', '*.log'],
150
- });
151
- console.log(`\n Using ${projectDir}`);
152
- // Sync: push local files up first, then pull any remote-only files down (non-fatal)
153
- try {
154
- const upResult = await syncUp();
155
- if (upResult.pushed > 0) {
156
- console.log(` Pushed ${upResult.pushed} file${upResult.pushed > 1 ? 's' : ''} to Gipity.`);
157
- }
158
- const downResult = await syncDown({ confirmDeletions: true });
159
- if (downResult.pulled > 0) {
160
- console.log(` Pulled ${downResult.pulled} file${downResult.pulled > 1 ? 's' : ''} from Gipity.`);
161
- }
162
- }
163
- catch {
164
- console.log(' Could not sync files (will retry on next prompt).');
165
- }
166
- // ── Step 2b: Project type (new projects only) ───────────────────
167
- if (isNewProject) {
168
- const projectType = await pickProjectType();
169
- initialPrompt = projectType.initialPrompt;
170
- if (projectType.scaffoldType) {
171
- console.log(` Scaffolding ${projectType.label}...`);
172
- try {
173
- await post(`/projects/${project.short_guid}/scaffold`, {
174
- title: project.name,
175
- type: projectType.scaffoldType,
176
- });
177
- const scaffoldSync = await syncDown();
178
- if (scaffoldSync.pulled > 0) {
179
- console.log(` Created ${scaffoldSync.pulled} starter file${scaffoldSync.pulled > 1 ? 's' : ''}.`);
180
- }
181
- }
182
- catch {
183
- console.log(' Scaffolding failed — starting with empty project.');
184
- }
185
- }
186
- }
187
- setupClaudeHooks();
188
- setupClaudeMd();
189
- setupGitignore();
190
- console.log(` ${success(`Project "${project.name}" ready.`)}\n`);
191
- }
192
- // ── Step 3: Launch Claude Code ────────────────────────────────────
193
- if (opts.claude === false) {
194
- console.log(` Done. cd ${process.cwd()} && claude`);
195
- return;
196
- }
197
- // Check claude is installed
198
- try {
199
- const checkCmd = process.platform === 'win32' ? 'where claude' : 'which claude';
200
- execSync(checkCmd, { stdio: 'ignore' });
201
- }
202
- catch {
203
- console.log(' Claude Code not found. Install it: npm install -g @anthropic-ai/claude-code');
204
- console.log(` Then: cd ${process.cwd()} && claude`);
205
- return;
206
- }
207
- // Pass through all unknown args to claude (everything after 'start-cc')
208
- const startCcIdx = process.argv.indexOf('start-cc');
209
- const knownFlags = ['--no-claude', '--api-base'];
210
- const claudeArgs = process.argv.slice(startCcIdx + 1).filter(arg => {
211
- for (const flag of knownFlags) {
212
- if (arg === flag)
213
- return false;
214
- if (arg.startsWith('--api-base='))
215
- return false;
216
- }
217
- return true;
218
- });
219
- // Filter out the value after --api-base if space-separated and after start-cc
220
- const apiBaseIdx = process.argv.indexOf('--api-base', startCcIdx);
221
- if (apiBaseIdx !== -1) {
222
- const valueIdx = claudeArgs.indexOf(process.argv[apiBaseIdx + 1]);
223
- if (valueIdx !== -1)
224
- claudeArgs.splice(valueIdx, 1);
225
- }
226
- console.log(` ${info('Launching Claude Code...')}\n`);
227
- const allArgs = initialPrompt
228
- ? [initialPrompt, ...claudeArgs]
229
- : claudeArgs;
230
- // Resolve full path on Windows so we can avoid shell:true, which
231
- // passes args through cmd.exe and mangles quotes/special chars.
232
- const claudeCmd = resolveCommand('claude');
233
- const child = spawn(claudeCmd, allArgs, {
234
- stdio: 'inherit',
235
- cwd: process.cwd(),
236
- });
237
- child.on('exit', (code) => process.exit(code ?? 0));
238
- }
239
- catch (err) {
240
- console.error(`\n ${clrError(`Error: ${err.message}`)}`);
241
- process.exit(1);
242
- }
243
- });
244
- async function pickOrCreateProject(projects, existingSlugs) {
245
- const recent = projects.slice(0, 7);
246
- const hasMore = projects.length > 7;
247
- console.log(` ${bold('Choose project to open:')}\n`);
248
- console.log(` ${bold('1.')} Create new project`);
249
- recent.forEach((p, i) => console.log(` ${bold(`${i + 2}.`)} ${p.name} ${muted(`(${p.slug})`)}`));
250
- if (hasMore)
251
- console.log(` ${bold(`${recent.length + 2}.`)} Show all projects`);
252
- console.log('');
253
- const maxOption = hasMore ? recent.length + 2 : recent.length + 1;
254
- const idx = await pickOne('Choose', maxOption, 1);
255
- // Selected a recent project
256
- if (idx >= 2 && idx <= recent.length + 1)
257
- return { project: recent[idx - 2], isNew: false };
258
- // Show all projects
259
- if (hasMore && idx === recent.length + 2) {
260
- console.log('');
261
- console.log(` ${bold('All projects:')}`);
262
- projects.forEach((p, i) => console.log(` ${bold(`${i + 1}.`)} ${p.name} ${muted(`(${p.slug})`)}`));
263
- console.log('');
264
- const allChoice = await prompt(` Choose (1-${projects.length}): `);
265
- const allIdx = parseInt(allChoice, 10);
266
- if (allIdx >= 1 && allIdx <= projects.length)
267
- return { project: projects[allIdx - 1], isNew: false };
268
- }
269
- // Default (Enter) or 1 = create new project
270
- return { project: await createNewProject(existingSlugs), isNew: true };
271
- }
272
- async function createNewProject(existingSlugs) {
273
- const suggested = suggestProjectName(existingSlugs);
274
- console.log('');
275
- const name = await prompt(` ${bold('Project name')} [${bold(suggested)}]: `);
276
- const projectName = name || suggested;
277
- const projectSlug = slugify(projectName);
278
- if (!projectSlug) {
279
- console.error(` ${clrError('Invalid project name.')}`);
280
- process.exit(1);
281
- }
282
- console.log(` ${info(`Creating "${projectName}"...`)}`);
283
- const res = await post('/projects', { name: projectName, slug: projectSlug });
284
- console.log(` ${success('Created.')}`);
285
- return res.data;
286
- }
287
- const PROJECT_TYPE_OPTIONS = [
288
- {
289
- label: 'Start empty',
290
- initialPrompt: 'This is a new empty project on Gipity. Briefly introduce yourself, mention that I have access to cloud hosting, databases, server-side API endpoints, sandboxed code execution, image generation, speech, and web search, and ask me what I want to build.',
291
- },
292
- {
293
- label: 'Web app',
294
- scaffoldType: 'web',
295
- initialPrompt: 'This is a new web app project on Gipity. The project has been scaffolded with starter HTML/CSS/JS. Deploy it to dev so I can see it live. Then briefly introduce yourself, mention that I have access to databases, server-side API endpoints, image generation, web search, and cloud hosting, and ask me what I want to build.',
296
- },
297
- {
298
- label: '3D World game',
299
- scaffoldType: '3d-world',
300
- initialPrompt: 'This is a new 3D World game on Gipity. Deploy it to dev so I can play it right away. Then briefly introduce yourself, mention that I can customize the world, objects, physics, game logic, and multiplayer, and ask me what kind of game I want to make.',
301
- },
302
- ];
303
- async function pickProjectType() {
304
- console.log('');
305
- console.log(` ${bold('What kind of project?')}\n`);
306
- console.log(` ${bold('1.')} Start empty. ${muted('Build everything from scratch.')}`);
307
- console.log(` ${bold('2.')} Web app or game. ${muted('Scaffolds basic HTML/CSS/JS starter files.')}`);
308
- console.log(` ${bold('3.')} 3D World game. ${muted('Scaffolds a playable starter world.')}`);
309
- console.log('');
310
- const idx = await pickOne('Choose', 3, 1);
311
- return PROJECT_TYPE_OPTIONS[idx - 1];
312
- }
313
- //# sourceMappingURL=start-cc.js.map
@@ -1,18 +0,0 @@
1
- /**
2
- * Shared 3D World template description.
3
- * Single source of truth used by:
4
- * - server/src/services/skills/ (web agent skills)
5
- * - cli/src/setup.ts (CLAUDE.md template for Claude Code)
6
- */
7
- export const GIP_3DW_GUIDE = `## 3D World
8
-
9
- **3D World** is the 3D multiplayer game template on Gipity. All 3D World games share the same visual style, physics engine (Rapier), and multiplayer backend (Colyseus). All files are fully editable.
10
-
11
- Scaffold a 3D World project with \`app_scaffold type=3d-world\` (web agent) or \`gipity scaffold --type 3d-world\` (CLI). This creates a playable 3D game with Three.js + Rapier physics + Colyseus multiplayer. Key files: \`config.js\` (metadata), \`settings.js\` (tunable values), \`strings.js\` (display text), \`objects.js\` (entity factories), \`game.js\` (orchestrator), plus engine files (\`core.js\`, \`world.js\`, \`physics.js\`, etc.).
12
-
13
- **Genres:** obby/parkour, tycoon, simulator, PvP combat, shooter, tower defense, horror, racing, RPG, social.
14
-
15
- **Features:** Opt-in gameplay modules enabled via \`config.features\`. Available: \`rocket-launcher\` (projectile weapon with physics explosions). Example: \`features: { 'rocket-launcher': true }\` in config.js. Features auto-initialize during boot.
16
-
17
- Regular game requests ("make a wordle", "build a quiz") should use the standard web scaffold — they don't need the 3D template.`;
18
- //# sourceMappingURL=gip3dw-guide.js.map