gipity 1.0.414 → 1.0.417
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/commands/chat.js +11 -5
- package/dist/commands/project.js +25 -14
- package/dist/commands/records.js +1 -1
- package/dist/commands/service.js +1 -1
- package/dist/commands/setup.js +1 -1
- package/dist/commands/skill.js +1 -1
- package/dist/commands/text.js +1 -1
- package/dist/commands/uninstall.js +73 -5
- package/dist/index.js +103 -15
- package/dist/knowledge.js +1 -0
- package/dist/relay/onboarding.js +61 -5
- package/dist/relay/setup.js +29 -2
- package/package.json +1 -1
package/dist/commands/chat.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { get, post, put, del } from '../api.js';
|
|
3
|
-
import { resolveProjectContext, saveConfig } from '../config.js';
|
|
3
|
+
import { resolveProjectContext, requireConfig, saveConfig } from '../config.js';
|
|
4
4
|
import { sync } from '../sync.js';
|
|
5
5
|
import { error as clrError, muted, success } from '../colors.js';
|
|
6
6
|
import { run, printList, printResult } from '../helpers/index.js';
|
|
@@ -98,13 +98,19 @@ chatCommand
|
|
|
98
98
|
});
|
|
99
99
|
}));
|
|
100
100
|
chatCommand
|
|
101
|
-
.command('rename <
|
|
102
|
-
.description('Rename a chat')
|
|
101
|
+
.command('rename <title...>')
|
|
102
|
+
.description('Rename a chat (the current chat by default; changes the tab title only)')
|
|
103
|
+
.option('--guid <guid>', 'Chat guid to rename (defaults to the current chat)')
|
|
103
104
|
.option('--json', 'Output as JSON')
|
|
104
|
-
.action((
|
|
105
|
+
.action((titleParts, opts) => run('Rename', async () => {
|
|
105
106
|
const title = titleParts.join(' ');
|
|
107
|
+
const guid = opts.guid || requireConfig().conversationGuid;
|
|
108
|
+
if (!guid) {
|
|
109
|
+
console.error(clrError('No current chat. Start a chat first, or pass --guid <guid>.'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
106
112
|
await put(`/conversations/${guid}`, { title });
|
|
107
|
-
printResult(success(`Renamed
|
|
113
|
+
printResult(success(`Renamed chat → "${title}".`), opts, { guid, title });
|
|
108
114
|
}));
|
|
109
115
|
chatCommand
|
|
110
116
|
.command('archive <guid>')
|
package/dist/commands/project.js
CHANGED
|
@@ -36,6 +36,31 @@ export const projectCommand = new Command('project')
|
|
|
36
36
|
return `${p.slug}${active}${def}`;
|
|
37
37
|
});
|
|
38
38
|
}));
|
|
39
|
+
projectCommand
|
|
40
|
+
.command('rename <name...>')
|
|
41
|
+
.description('Rename the current project (display name only; slug and URLs unchanged)')
|
|
42
|
+
.option('--project <target>', 'Project name, slug, or guid to rename (defaults to the current project)')
|
|
43
|
+
.option('--json', 'Output as JSON')
|
|
44
|
+
.action((nameParts, opts) => run('Rename', async () => {
|
|
45
|
+
const name = nameParts.join(' ');
|
|
46
|
+
const config = requireConfig();
|
|
47
|
+
let guid = config.projectGuid;
|
|
48
|
+
if (opts.project) {
|
|
49
|
+
const res = await get('/projects?limit=1000');
|
|
50
|
+
const match = res.data.find(p => p.slug === opts.project || p.name === opts.project || p.short_guid === opts.project);
|
|
51
|
+
if (!match) {
|
|
52
|
+
console.error(clrError(`Project "${opts.project}" not found.`));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
guid = match.short_guid;
|
|
56
|
+
}
|
|
57
|
+
if (!guid) {
|
|
58
|
+
console.error(clrError('No current project. Link one first, or pass --project <name>.'));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
await put(`/projects/${guid}`, { name });
|
|
62
|
+
printResult(success(`Renamed project → "${name}".`), opts, { guid, name });
|
|
63
|
+
}));
|
|
39
64
|
projectCommand
|
|
40
65
|
.command('create <name>')
|
|
41
66
|
.description('Create a project')
|
|
@@ -132,20 +157,6 @@ projectCommand
|
|
|
132
157
|
await del(`/projects/${match.short_guid}`);
|
|
133
158
|
printResult(`Deleted "${match.name}".`, opts, { deleted: match.slug });
|
|
134
159
|
}));
|
|
135
|
-
projectCommand
|
|
136
|
-
.command('rename <name> <new-name>')
|
|
137
|
-
.description('Rename a project')
|
|
138
|
-
.option('--json', 'Output as JSON')
|
|
139
|
-
.action((name, newName, opts) => run('Rename', async () => {
|
|
140
|
-
const res = await get('/projects?limit=1000');
|
|
141
|
-
const match = res.data.find(p => p.slug === name || p.name === name || p.short_guid === name);
|
|
142
|
-
if (!match) {
|
|
143
|
-
console.error(clrError(`Project "${name}" not found.`));
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
await put(`/projects/${match.short_guid}`, { name: newName });
|
|
147
|
-
printResult(`Renamed "${match.name}" → "${newName}".`, opts, { renamed: match.slug, from: match.name, to: newName });
|
|
148
|
-
}));
|
|
149
160
|
projectCommand
|
|
150
161
|
.command('info')
|
|
151
162
|
.description('Show current project')
|
package/dist/commands/records.js
CHANGED
|
@@ -9,7 +9,7 @@ import { confirm } from '../utils.js';
|
|
|
9
9
|
// Records API is the only records surface that exists server-side; there is no
|
|
10
10
|
// /projects/<guid>/records mirror.)
|
|
11
11
|
export const recordsCommand = new Command('records')
|
|
12
|
-
.description('Manage records');
|
|
12
|
+
.description('Manage records-kit data (registry-driven CRUD)');
|
|
13
13
|
recordsCommand
|
|
14
14
|
.command('list')
|
|
15
15
|
.description('List record tables')
|
package/dist/commands/service.js
CHANGED
|
@@ -20,7 +20,7 @@ const SERVICES = [
|
|
|
20
20
|
{ name: 'location/geocode', method: 'POST', desc: 'Reverse geocode ({ lat, lon })' },
|
|
21
21
|
];
|
|
22
22
|
export const serviceCommand = new Command('service')
|
|
23
|
-
.description('Call an app service')
|
|
23
|
+
.description('Call an app service (llm, tts, image, transcribe, ...)')
|
|
24
24
|
.addHelpText('after', '\nCall a Gipity app service: LLM, image, music, TTS, and more.');
|
|
25
25
|
serviceCommand
|
|
26
26
|
.command('list')
|
package/dist/commands/setup.js
CHANGED
|
@@ -20,7 +20,7 @@ export const setupCommand = new Command('setup')
|
|
|
20
20
|
.description('Set up this computer as a relay (no project, no launch)')
|
|
21
21
|
.action(async () => {
|
|
22
22
|
try {
|
|
23
|
-
|
|
23
|
+
// Leading blank comes from the central output frame (installOutputFrame).
|
|
24
24
|
console.log(` ${bold('Gipity setup')} ${muted('- get this computer ready as a relay')}`);
|
|
25
25
|
console.log('');
|
|
26
26
|
// ── Step 1: Auth ──────────────────────────────────────────────────
|
package/dist/commands/skill.js
CHANGED
|
@@ -17,7 +17,7 @@ function readInstalledKitReadme(name) {
|
|
|
17
17
|
return existsSync(readme) ? readFileSync(readme, 'utf-8') : null;
|
|
18
18
|
}
|
|
19
19
|
export const skillCommand = new Command('skill')
|
|
20
|
-
.description('
|
|
20
|
+
.description('Task docs - read the matching skill before building');
|
|
21
21
|
skillCommand
|
|
22
22
|
.command('list')
|
|
23
23
|
.description('List skills')
|
package/dist/commands/text.js
CHANGED
|
@@ -140,7 +140,7 @@ const analyzeCommand = new Command('analyze')
|
|
|
140
140
|
// Parent namespace. One capability today (analyze); namespaced for a lean
|
|
141
141
|
// top-level surface and room for future text operations.
|
|
142
142
|
export const textCommand = new Command('text')
|
|
143
|
-
.description('
|
|
143
|
+
.description('Deterministic text analysis (counts, substrings) - no LLM needed')
|
|
144
144
|
.addHelpText('after', `\nDeterministic char/word counts, frequency, and occurrences (${brand('text analyze')}). Local, no network.`)
|
|
145
145
|
.addCommand(analyzeCommand);
|
|
146
146
|
textCommand.action(() => {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { Command } from 'commander';
|
|
12
12
|
import { existsSync, rmSync, unlinkSync, readFileSync, writeFileSync } from 'fs';
|
|
13
|
-
import { homedir } from 'os';
|
|
13
|
+
import { homedir, platform as osPlatform } from 'os';
|
|
14
14
|
import { join, resolve } from 'path';
|
|
15
15
|
import { spawnSyncCommand } from '../platform.js';
|
|
16
16
|
import { post } from '../api.js';
|
|
@@ -52,6 +52,45 @@ function removeGipityPluginConfig() {
|
|
|
52
52
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
53
53
|
return changed;
|
|
54
54
|
}
|
|
55
|
+
/** The install.sh / install.ps1 launcher appends a line to the user's shell rc
|
|
56
|
+
* files putting ~/.gipity/launcher/bin on PATH. Once ~/.gipity is deleted that
|
|
57
|
+
* line points at nothing, so strip it back out - matched by the installer's own
|
|
58
|
+
* marker comment and the launcher bin path. Unix shells only; on Windows the
|
|
59
|
+
* installer edits the user PATH env var, which we leave untouched. Returns the
|
|
60
|
+
* rc files we actually changed. */
|
|
61
|
+
function removeInstallerPathLines() {
|
|
62
|
+
if (osPlatform() === 'win32')
|
|
63
|
+
return [];
|
|
64
|
+
const marker = '# Added by the Gipity installer';
|
|
65
|
+
const touched = [];
|
|
66
|
+
for (const name of ['.bashrc', '.zshrc', '.profile']) {
|
|
67
|
+
const rc = join(homedir(), name);
|
|
68
|
+
if (!existsSync(rc))
|
|
69
|
+
continue;
|
|
70
|
+
let text;
|
|
71
|
+
try {
|
|
72
|
+
text = readFileSync(rc, 'utf-8');
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const lines = text.split('\n');
|
|
78
|
+
const kept = lines.filter((line) => line.trim() !== marker && !line.includes('.gipity/launcher/bin'));
|
|
79
|
+
if (kept.length === lines.length)
|
|
80
|
+
continue;
|
|
81
|
+
try {
|
|
82
|
+
writeFileSync(rc, kept.join('\n'));
|
|
83
|
+
touched.push(rc);
|
|
84
|
+
}
|
|
85
|
+
catch { /* ignore */ }
|
|
86
|
+
}
|
|
87
|
+
return touched;
|
|
88
|
+
}
|
|
89
|
+
/** Render an absolute path under $HOME back to ~ for display. */
|
|
90
|
+
function tildify(p) {
|
|
91
|
+
const home = homedir();
|
|
92
|
+
return p === home || p.startsWith(home + '/') ? '~' + p.slice(home.length) : p;
|
|
93
|
+
}
|
|
55
94
|
function resolveCliPath() {
|
|
56
95
|
return resolve(process.argv[1] ?? 'gipity');
|
|
57
96
|
}
|
|
@@ -129,14 +168,28 @@ export const uninstallCommand = new Command('uninstall')
|
|
|
129
168
|
.action(async (opts) => {
|
|
130
169
|
const autoYes = opts.yes || getAutoConfirm();
|
|
131
170
|
const gipityDir = join(homedir(), '.gipity');
|
|
171
|
+
// Installs from install.sh / install.ps1 keep the launcher binary itself
|
|
172
|
+
// under ~/.gipity/launcher/bin, so wiping ~/.gipity removes the binary too -
|
|
173
|
+
// the only leftover is the PATH line the installer added to the shell rc
|
|
174
|
+
// files (cleaned up below). An npm-global install instead leaves the binary
|
|
175
|
+
// in npm's bin dir, which the user removes with `npm uninstall -g gipity`.
|
|
176
|
+
const launcherBin = join(gipityDir, 'launcher', 'bin', 'gipity');
|
|
177
|
+
const installedViaLauncher = existsSync(launcherBin);
|
|
132
178
|
console.log(`${bold('Gipity uninstall')} - this will:`);
|
|
133
179
|
console.log(`• Stop the running relay daemon (if any)`);
|
|
134
180
|
console.log(`• Remove the OS autostart service (launchd / systemd / Task Scheduler)`);
|
|
135
181
|
console.log(`• Revoke this device on the server (best-effort)`);
|
|
136
182
|
console.log(`• Remove the Gipity Claude Code plugin enablement (all Gipity hooks)`);
|
|
137
|
-
console.log(`• Delete ${gipityDir}
|
|
183
|
+
console.log(`• Delete ${gipityDir}/${installedViaLauncher ? dim(' (includes the gipity launcher binary itself)') : ''}`);
|
|
184
|
+
if (installedViaLauncher)
|
|
185
|
+
console.log(`• Remove the launcher PATH line from your shell startup files`);
|
|
138
186
|
console.log('');
|
|
139
|
-
|
|
187
|
+
if (installedViaLauncher) {
|
|
188
|
+
console.log(`${dim('The `gipity` launcher lives under ~/.gipity, so this removes the binary too - no separate `npm uninstall` needed.')}`);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.log(`${dim('It will NOT remove the `gipity` binary. Run `npm uninstall -g gipity` afterward if you want that too.')}`);
|
|
192
|
+
}
|
|
140
193
|
console.log('');
|
|
141
194
|
if (!autoYes) {
|
|
142
195
|
const ok = await confirm('Proceed?');
|
|
@@ -179,8 +232,23 @@ export const uninstallCommand = new Command('uninstall')
|
|
|
179
232
|
else {
|
|
180
233
|
console.log(`${muted(`${gipityDir}/ already gone.`)}`);
|
|
181
234
|
}
|
|
235
|
+
// 6. Strip the launcher PATH line the installer added to shell rc files, so
|
|
236
|
+
// we don't leave a dangling entry pointing at the dir we just deleted.
|
|
237
|
+
const touchedRc = removeInstallerPathLines();
|
|
238
|
+
if (touchedRc.length) {
|
|
239
|
+
console.log(`${success('Removed the launcher PATH line from')} ${dim(touchedRc.map(tildify).join(', '))}`);
|
|
240
|
+
}
|
|
182
241
|
console.log('');
|
|
183
|
-
console.log(`${success('Uninstall complete.')}
|
|
184
|
-
|
|
242
|
+
console.log(`${success('Uninstall complete.')}`);
|
|
243
|
+
if (installedViaLauncher) {
|
|
244
|
+
console.log(`${dim('The launcher binary was under ~/.gipity and is now gone - nothing left to remove.')}`);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
console.log(`${dim('Run')} ${brand('npm uninstall -g gipity')} ${dim('to remove the `gipity` binary too.')}`);
|
|
248
|
+
}
|
|
249
|
+
// The current shell caches the removed binary's path in its command hash, so
|
|
250
|
+
// the next `gipity` here reports "No such file or directory" until it's
|
|
251
|
+
// cleared. A new terminal always fixes it; `hash -r` fixes this one.
|
|
252
|
+
console.log(`${dim('Open a new terminal - or run')} ${brand('hash -r')} ${dim('in this one - so your shell forgets the removed binary’s path.')}`);
|
|
185
253
|
});
|
|
186
254
|
//# sourceMappingURL=uninstall.js.map
|
package/dist/index.js
CHANGED
|
@@ -127,25 +127,55 @@ const program = new Command();
|
|
|
127
127
|
// global value-option precedes it (e.g. `gipity --api-base X workflow create
|
|
128
128
|
// --from Y`). enablePositionalOptions draws the boundary at the first command.
|
|
129
129
|
program.enablePositionalOptions();
|
|
130
|
-
// ── Command groups (logical ordering within each)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const
|
|
130
|
+
// ── Command groups (lifecycle order; logical ordering within each) ─────
|
|
131
|
+
// Section names deliberately match the vocabulary of the generated CLAUDE.md
|
|
132
|
+
// primer and the skill docs ("build loop: add → edit → deploy → page
|
|
133
|
+
// inspect"), so an agent's first `--help` reads in the same terms as the rest
|
|
134
|
+
// of its context. Humans get the same win: sections follow the order you
|
|
135
|
+
// actually use them - orient, build, wire the backend, then everything else.
|
|
136
|
+
const startGroup = [statusCommand, initCommand, skillCommand, projectCommand];
|
|
137
|
+
const buildGroup = [addCommand, removeCommand, saveCommand, loadCommand, deployCommand, pageCommand, testCommand];
|
|
138
|
+
const backendGroup = [dbCommand, fnCommand, secretsCommand, logsCommand, jobCommand, workflowCommand];
|
|
139
|
+
const servicesGroup = [serviceCommand, generateCommand, notifyCommand, paymentsCommand, realtimeCommand, recordsCommand, rbacCommand, auditCommand, domainCommand, tokenCommand];
|
|
140
|
+
const filesGroup = [syncCommand, fileCommand, pushCommand, uploadCommand, storageCommand];
|
|
141
|
+
const gipGroup = [chatCommand, memoryCommand, agentCommand, approvalCommand, gmailCommand];
|
|
142
|
+
const utilitiesGroup = [sandboxCommand, emailCommand, locationCommand, textCommand, bugCommand];
|
|
143
|
+
const connectGroup = [loginCommand, logoutCommand, claudeCommand, setupCommand, relayCommand, githubCommand, creditsCommand, doctorCommand, updateCommand, uninstallCommand];
|
|
139
144
|
const HELP_SECTIONS = [
|
|
140
|
-
{ title: '
|
|
141
|
-
{ title: '
|
|
142
|
-
{ title: '
|
|
145
|
+
{ title: 'Start here', cmds: startGroup },
|
|
146
|
+
{ title: 'App build & ship', cmds: buildGroup },
|
|
147
|
+
{ title: 'App backend', cmds: backendGroup },
|
|
148
|
+
{ title: 'App services', cmds: servicesGroup },
|
|
143
149
|
{ title: 'Files', cmds: filesGroup },
|
|
144
|
-
{ title: '
|
|
150
|
+
{ title: 'Gip (cloud agent)', cmds: gipGroup },
|
|
145
151
|
{ title: 'Utilities', cmds: utilitiesGroup },
|
|
146
|
-
{ title: '
|
|
147
|
-
{ title: 'Setup', cmds: setupGroup },
|
|
152
|
+
{ title: 'Connect & setup', cmds: connectGroup },
|
|
148
153
|
];
|
|
154
|
+
// Per-command deep-docs cross-links, rendered as a "Docs:" epilog on each
|
|
155
|
+
// command's --help AND carried in the `help --json` manifest. This is the
|
|
156
|
+
// bridge agents actually need - from "found the command" to "know the API
|
|
157
|
+
// pattern" - without duplicating skill content in help text (skills stay the
|
|
158
|
+
// single source of truth; this maps names only). Keys must be real skill
|
|
159
|
+
// names (`gipity skill list`).
|
|
160
|
+
const SKILL_DOCS = {
|
|
161
|
+
save: 'app-import',
|
|
162
|
+
load: 'app-import',
|
|
163
|
+
github: 'app-import',
|
|
164
|
+
deploy: 'deploy',
|
|
165
|
+
page: 'app-debugging',
|
|
166
|
+
test: 'app-testing',
|
|
167
|
+
db: 'app-database',
|
|
168
|
+
fn: 'app-development',
|
|
169
|
+
service: 'service-call',
|
|
170
|
+
notify: 'app-notify',
|
|
171
|
+
payments: 'app-payments',
|
|
172
|
+
realtime: 'app-realtime',
|
|
173
|
+
job: 'jobs',
|
|
174
|
+
sandbox: 'sandbox-tools',
|
|
175
|
+
gmail: 'google-services',
|
|
176
|
+
email: 'email',
|
|
177
|
+
location: 'location',
|
|
178
|
+
};
|
|
149
179
|
program
|
|
150
180
|
.name('gipity')
|
|
151
181
|
.description(`${brand(bold('Gipity CLI'))} ${dim('-')} ${GIPITY_TAGLINE.replace(/\.$/, '')}\n\n ${dim('Hosting, databases, deploys, workflows, code execution, and monitoring - one place, agent-tuned. Pair with Claude Code or use standalone.')}`)
|
|
@@ -207,14 +237,72 @@ program.configureHelp({
|
|
|
207
237
|
lines.push('');
|
|
208
238
|
}
|
|
209
239
|
lines.push(dim(`Run "${cmd.name()} <command> --help" for details on a specific command.`));
|
|
240
|
+
lines.push(dim(`Deep docs: "${cmd.name()} skill list". Machine-readable manifest: "${cmd.name()} help --json".`));
|
|
210
241
|
lines.push('');
|
|
211
242
|
return lines.join('\n');
|
|
212
243
|
},
|
|
213
244
|
});
|
|
214
245
|
for (const cmd of HELP_SECTIONS.flatMap(s => s.cmds)) {
|
|
215
246
|
configureHelp(cmd);
|
|
247
|
+
// "Docs:" epilog bridging this command's --help to its skill doc.
|
|
248
|
+
const skill = SKILL_DOCS[cmd.name()];
|
|
249
|
+
if (skill)
|
|
250
|
+
cmd.addHelpText('after', `Docs: gipity skill read ${skill}\n`);
|
|
216
251
|
program.addCommand(cmd);
|
|
217
252
|
}
|
|
253
|
+
// ── `gipity help [command]` + `help --json` machine-readable manifest ───
|
|
254
|
+
// The JSON manifest is generated from the SAME commander registry that
|
|
255
|
+
// renders human help, so it cannot drift: name, args, options, subcommands,
|
|
256
|
+
// group, and the skill cross-link per command. Meant for agents and tooling
|
|
257
|
+
// that want the full surface in one parseable shot instead of N --help calls.
|
|
258
|
+
program.helpCommand(false); // replace the builtin so we can add --json
|
|
259
|
+
function manifestCommand(c) {
|
|
260
|
+
return {
|
|
261
|
+
name: c.name(),
|
|
262
|
+
aliases: c.aliases().slice(),
|
|
263
|
+
description: c.description(),
|
|
264
|
+
usage: `gipity ${fullCommandPath(c)} ${c.usage()}`.trim(),
|
|
265
|
+
args: c.registeredArguments.map(a => ({
|
|
266
|
+
name: a.name(),
|
|
267
|
+
required: a.required,
|
|
268
|
+
description: a.description || '',
|
|
269
|
+
})),
|
|
270
|
+
options: c.options.filter(o => !o.hidden).map(o => ({ flags: o.flags, description: o.description })),
|
|
271
|
+
subcommands: c.commands.filter(sc => !sc._hidden).map(manifestCommand),
|
|
272
|
+
skill: SKILL_DOCS[c.name()] ?? null,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function fullCommandPath(c) {
|
|
276
|
+
const parts = [];
|
|
277
|
+
for (let cur = c; cur && cur.parent; cur = cur.parent)
|
|
278
|
+
parts.unshift(cur.name());
|
|
279
|
+
return parts.join(' ');
|
|
280
|
+
}
|
|
281
|
+
program.addCommand(new Command('help')
|
|
282
|
+
.description('Show help; --json emits the full command manifest for agents/tools')
|
|
283
|
+
.argument('[command]', 'Show help for this command')
|
|
284
|
+
.option('--json', 'Output every command (args, options, subcommands, docs links) as JSON')
|
|
285
|
+
.action((name, opts) => {
|
|
286
|
+
if (opts.json) {
|
|
287
|
+
const manifest = {
|
|
288
|
+
name: 'gipity',
|
|
289
|
+
version: pkg.version,
|
|
290
|
+
docs: 'Run `gipity skill list` for deep task docs; each command may carry a `skill` cross-link.',
|
|
291
|
+
sections: HELP_SECTIONS.map(s => ({
|
|
292
|
+
title: s.title,
|
|
293
|
+
commands: s.cmds.map(manifestCommand),
|
|
294
|
+
})),
|
|
295
|
+
};
|
|
296
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (name) {
|
|
300
|
+
const target = program.commands.find(c => c.name() === name || c.aliases().includes(name));
|
|
301
|
+
if (target)
|
|
302
|
+
target.help();
|
|
303
|
+
}
|
|
304
|
+
program.help();
|
|
305
|
+
}));
|
|
218
306
|
// ── Malformed invocation → print the command's help inline, error LAST ──
|
|
219
307
|
// When an agent guesses the wrong shape (excess args, unknown command/option,
|
|
220
308
|
// missing arg), don't make it run `--help` as a second trip: render that exact
|
package/dist/knowledge.js
CHANGED
|
@@ -107,6 +107,7 @@ mkdir -p ~/GipityProjects/<slug> && cd ~/GipityProjects/<slug> && gipity init <s
|
|
|
107
107
|
## CLI quick reference
|
|
108
108
|
|
|
109
109
|
Key commands: \`gipity add <template|kit>\`, \`gipity deploy dev\`, \`gipity sandbox run\`, \`gipity page inspect <url>\`, \`gipity page screenshot <url>\`, \`gipity db query "SQL"\`, \`gipity fn call <name>\`, \`gipity logs fn <name>\`, \`gipity secrets set <NAME> <value> [--account]\` (store an API key/token encrypted; read in functions via \`secrets.get('NAME')\` — never hardcode keys), \`gipity email send --to <addr> --subject <s> --body <b>\` (sends as \`gipity@gipity.ai\`; omit \`--to\` to self-send), \`gipity skill read <name>\`.
|
|
110
|
+
Rename for findability: \`gipity project rename <name>\` renames the current project's display name (the slug and deployed URLs never change); \`gipity chat rename <title>\` renames the current chat's tab title. Both are the display label users scan to switch between tabs — retitle a chat when the conversation clearly shifts to a new topic (sparingly, not every turn), and keep every project/chat title SHORT: 2-4 words, ≤40 characters, no trailing punctuation (e.g. "Stripe checkout", "Tetris game").
|
|
110
111
|
Pull an existing remote project local (given its URL/slug): \`mkdir -p ~/GipityProjects/<slug> && cd ~/GipityProjects/<slug> && gipity init <slug>\` (adopts the matching project and syncs files down - this is the "clone").
|
|
111
112
|
Move whole apps in/out: \`gipity save\` (export this project as a portable \`.gip\` bundle), \`gipity load <file.gip | github:owner/repo>\` (import as a NEW project; \`--inspect\` to preview), \`gipity github connect\` (1-2 click GitHub access for imports). Porting a Vercel/Replit/Lovable app? Load the \`app-import\` skill first.
|
|
112
113
|
For deterministic text questions (letter/word counts, substring occurrences, nth word/char, anagrams), use \`gipity text analyze "<text>"\` - local and instant, no sandbox or LLM needed.
|
package/dist/relay/onboarding.js
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
* All the non-interactive primitives live in `setup.ts` (`pairDevice`,
|
|
13
13
|
* `startDaemon`, `installAutostart`); this module is only the prompts + copy.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
16
|
import { prompt, confirm } from '../utils.js';
|
|
17
17
|
import { bold, brand, dim, success, error as clrError, muted } from '../colors.js';
|
|
18
18
|
import * as state from './state.js';
|
|
19
19
|
import { UnsupportedPlatformError } from './installers.js';
|
|
20
|
-
import { pairDevice, startDaemon, installAutostart } from './setup.js';
|
|
20
|
+
import { pairDevice, startDaemon, installAutostart, friendlyDeviceName } from './setup.js';
|
|
21
21
|
/** Spawn a fresh `gipity relay run` detached from this process. Fire-and-forget.
|
|
22
22
|
* Re-exported from the shared `setup` core so existing importers (`claude.ts`)
|
|
23
23
|
* keep their import path. */
|
|
@@ -27,6 +27,21 @@ export const ensureDaemonRunning = startDaemon;
|
|
|
27
27
|
* ended up enabled (or was already), `false` if the user declined or a step
|
|
28
28
|
* failed. Non-interactive flows (e.g. `gipity claude -p`) must not call this.
|
|
29
29
|
*/
|
|
30
|
+
/** True inside Windows Subsystem for Linux (either env marker or kernel
|
|
31
|
+
* string). Used to explain autostart failures accurately - WSL ships without
|
|
32
|
+
* a systemd user session unless the user opts in via /etc/wsl.conf. */
|
|
33
|
+
function isWsl() {
|
|
34
|
+
if (process.platform !== 'linux')
|
|
35
|
+
return false;
|
|
36
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP)
|
|
37
|
+
return true;
|
|
38
|
+
try {
|
|
39
|
+
return /microsoft/i.test(readFileSync('/proc/version', 'utf-8'));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
30
45
|
export async function runRelaySetup(opts) {
|
|
31
46
|
const alreadyAnswered = state.getRelayEnabled() !== undefined;
|
|
32
47
|
// `gipity claude` first-run: honor the user's prior choice and don't re-ask.
|
|
@@ -35,6 +50,36 @@ export async function runRelaySetup(opts) {
|
|
|
35
50
|
ensureDaemonRunning();
|
|
36
51
|
return state.isRelayEnabled();
|
|
37
52
|
}
|
|
53
|
+
// We only ever REGISTER an unregistered machine. If this computer already has
|
|
54
|
+
// a device, re-running setup must not silently create a second one — nor
|
|
55
|
+
// quietly reuse the old name after asking for a new one (the original bug:
|
|
56
|
+
// you'd type a new name and it would still show the old one). Surface the
|
|
57
|
+
// existing registration plainly and point at revoke as the way to start over.
|
|
58
|
+
const existingDevice = state.getDevice();
|
|
59
|
+
if (existingDevice) {
|
|
60
|
+
// Keep setup's promise: leave the relay enabled and running.
|
|
61
|
+
state.setRelayEnabled(true);
|
|
62
|
+
if (!state.isPaused())
|
|
63
|
+
ensureDaemonRunning();
|
|
64
|
+
if (opts.mode === 'run-now') {
|
|
65
|
+
console.log(` ${bold('This computer is already set up as a relay')}`);
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log(` ${success('Registered as')} ${bold(existingDevice.name)} ${muted(`(${existingDevice.guid})`)}`);
|
|
68
|
+
if (state.isPaused()) {
|
|
69
|
+
console.log(` ${dim('The relay is paused — resume it with')} ${brand('gipity relay resume')}${dim('.')}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log(` ${dim('The relay is running. Open')} ${brand('gipity.ai')}${dim(' and type `/claude` to drive Claude Code here.')}`);
|
|
73
|
+
}
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(` ${dim('To register this computer again — for example under a different name —')}`);
|
|
76
|
+
console.log(` ${dim('unregister it first, then re-run setup:')}`);
|
|
77
|
+
console.log(` ${brand('gipity relay revoke')} ${dim('# unpairs this computer and removes the login service')}`);
|
|
78
|
+
console.log(` ${brand('gipity setup')} ${dim('# register it again (asks for a new name)')}`);
|
|
79
|
+
console.log('');
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
38
83
|
// Header. `gipity setup` frames it as the deliberate action it is; the
|
|
39
84
|
// `gipity claude` first-run frames it as an optional add-on it's offering.
|
|
40
85
|
if (opts.mode === 'run-now') {
|
|
@@ -59,8 +104,8 @@ export async function runRelaySetup(opts) {
|
|
|
59
104
|
console.log('');
|
|
60
105
|
return false;
|
|
61
106
|
}
|
|
62
|
-
// Device name - show
|
|
63
|
-
const defaultName =
|
|
107
|
+
// Device name - show a friendly default (owner + device kind); Enter accepts.
|
|
108
|
+
const defaultName = friendlyDeviceName();
|
|
64
109
|
const rawName = await prompt(` Device name [${bold(defaultName)}]: `);
|
|
65
110
|
const name = (rawName || defaultName).trim();
|
|
66
111
|
if (!name || name.length > 100) {
|
|
@@ -94,7 +139,18 @@ export async function runRelaySetup(opts) {
|
|
|
94
139
|
try {
|
|
95
140
|
const res = installAutostart();
|
|
96
141
|
if (!res.ok) {
|
|
97
|
-
|
|
142
|
+
if (isWsl()) {
|
|
143
|
+
// WSL: `systemctl --user` fails unless systemd is enabled in
|
|
144
|
+
// /etc/wsl.conf, so name the actual cause + the fix instead of a
|
|
145
|
+
// generic non-zero. The relay itself is unaffected (it's already
|
|
146
|
+
// running this session, and `gipity claude` restarts it).
|
|
147
|
+
console.log(` ${muted('Auto-start needs systemd, which this WSL distro has off. The relay still runs -')}`);
|
|
148
|
+
console.log(` ${muted('it started just now and `gipity claude` restarts it. For boot-time auto-start:')}`);
|
|
149
|
+
console.log(` ${muted('add [boot] systemd=true to /etc/wsl.conf, restart WSL, then run')} ${brand('gipity relay install')}${muted('.')}`);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log(` ${muted('Autostart install returned non-zero - you can run')} ${brand('gipity relay install')} ${muted('later.')}`);
|
|
153
|
+
}
|
|
98
154
|
}
|
|
99
155
|
else {
|
|
100
156
|
console.log(` ${success('Auto-start installed.')} ${dim(res.summary)}`);
|
package/dist/relay/setup.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* and no `process.exit` in this module: callers own all UX.
|
|
12
12
|
*/
|
|
13
13
|
import { spawnCommand, spawnSyncCommand } from '../platform.js';
|
|
14
|
-
import { hostname, platform as osPlatform } from 'os';
|
|
14
|
+
import { hostname, userInfo, platform as osPlatform } from 'os';
|
|
15
15
|
import { resolve, dirname } from 'path';
|
|
16
16
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
17
17
|
import { post } from '../api.js';
|
|
@@ -24,6 +24,33 @@ export function mapPlatform(p) {
|
|
|
24
24
|
return p;
|
|
25
25
|
return 'linux';
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* A friendly default device name for the web CLI — e.g. "Steve's Mac",
|
|
29
|
+
* "Steve's Windows PC". Prefers the OS login name + device kind, which reads
|
|
30
|
+
* better and is more recognizable than a raw hostname like "SignalOrangeXps".
|
|
31
|
+
* Falls back to the hostname, then a generic label. The user can override at
|
|
32
|
+
* the prompt, and the server de-dupes collisions ("Steve's Mac-2").
|
|
33
|
+
*/
|
|
34
|
+
export function friendlyDeviceName() {
|
|
35
|
+
const kind = osPlatform() === 'darwin' ? 'Mac' :
|
|
36
|
+
osPlatform() === 'win32' ? 'Windows PC' :
|
|
37
|
+
'Linux PC';
|
|
38
|
+
let user = '';
|
|
39
|
+
try {
|
|
40
|
+
user = (userInfo().username || '').trim();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// userInfo() throws when there's no matching passwd entry (some containers).
|
|
44
|
+
}
|
|
45
|
+
// Generic/service accounts carry no signal — the hostname is more useful there.
|
|
46
|
+
const generic = new Set(['root', 'user', 'admin', 'ubuntu', 'ec2-user', 'administrator', 'pi']);
|
|
47
|
+
if (user && !generic.has(user.toLowerCase())) {
|
|
48
|
+
const owner = user.charAt(0).toUpperCase() + user.slice(1);
|
|
49
|
+
return `${owner}'s ${kind}`;
|
|
50
|
+
}
|
|
51
|
+
const host = (hostname() || '').trim();
|
|
52
|
+
return host || `My ${kind}`;
|
|
53
|
+
}
|
|
27
54
|
/** Absolute path to the currently-running `gipity` CLI. Embedded in service
|
|
28
55
|
* units so launchd/systemd/Task Scheduler re-launch the same binary even if
|
|
29
56
|
* PATH changes. */
|
|
@@ -59,7 +86,7 @@ export async function pairDevice(opts = {}) {
|
|
|
59
86
|
}
|
|
60
87
|
state.clearDevice();
|
|
61
88
|
}
|
|
62
|
-
const name = (opts.name?.trim() ||
|
|
89
|
+
const name = (opts.name?.trim() || friendlyDeviceName()).trim();
|
|
63
90
|
if (!name || name.length > 100) {
|
|
64
91
|
throw new Error('Device name must be 1–100 non-whitespace characters.');
|
|
65
92
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gipity",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.417",
|
|
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",
|