gipity 1.0.408 → 1.0.410
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/claude-setup.js +3 -2
- package/dist/commands/add.js +11 -2
- package/dist/commands/approval.js +1 -1
- package/dist/commands/bug.js +69 -0
- package/dist/commands/credits.js +242 -15
- package/dist/commands/email.js +2 -1
- package/dist/commands/gmail.js +2 -1
- package/dist/commands/init.js +2 -1
- package/dist/commands/job.js +5 -4
- package/dist/commands/login.js +1 -0
- package/dist/commands/notify.js +2 -1
- package/dist/commands/page.js +1 -1
- package/dist/commands/payments.js +2 -1
- package/dist/commands/plan.js +1 -1
- package/dist/commands/push.js +7 -3
- package/dist/commands/relay.js +26 -2
- package/dist/commands/remove.js +10 -4
- package/dist/commands/secrets.js +2 -1
- package/dist/commands/service.js +2 -1
- package/dist/commands/storage.js +64 -0
- package/dist/commands/sync.js +2 -1
- package/dist/commands/text.js +2 -1
- package/dist/commands/token.js +2 -1
- package/dist/commands/uninstall.js +2 -2
- package/dist/commands/upload.js +1 -2
- package/dist/config.js +4 -4
- package/dist/helpers/output.js +42 -13
- package/dist/index.js +5 -4
- package/dist/knowledge.js +22 -0
- package/dist/platform.js +9 -5
- package/dist/relay/daemon.js +112 -7
- package/dist/relay/installers.js +10 -2
- package/dist/relay/onboarding.js +5 -2
- package/dist/relay/redact.js +26 -3
- package/dist/relay/setup.js +4 -4
- package/dist/relay/state.js +6 -3
- package/dist/setup.js +2 -3
- package/dist/sync.js +227 -106
- package/dist/updater/bootstrap.js +1 -1
- package/dist/updater/shim.js +4 -4
- package/dist/upload.js +1 -1
- package/package.json +6 -3
- package/dist/coding-guidelines.js +0 -52
- package/dist/commands/browser.js +0 -84
- package/dist/commands/checkpoint.js +0 -68
- package/dist/commands/hook-capture.js +0 -215
- package/dist/commands/scaffold.js +0 -58
- package/dist/commands/skills.js +0 -45
- package/dist/commands/start-cc.js +0 -313
- package/dist/gip3dw-guide.js +0 -18
- package/dist/platform-overview.js +0 -52
- package/dist/relay/transcripts.js +0 -119
package/dist/claude-setup.js
CHANGED
|
@@ -24,7 +24,7 @@ export function claudeInstallPlan(platformOverride) {
|
|
|
24
24
|
/** Whether the `claude` binary resolves on PATH. */
|
|
25
25
|
export function isClaudeInstalled(platformOverride) {
|
|
26
26
|
try {
|
|
27
|
-
execSync(claudeInstallPlan(platformOverride).checkCmd, { stdio: 'ignore' });
|
|
27
|
+
execSync(claudeInstallPlan(platformOverride).checkCmd, { stdio: 'ignore', windowsHide: true });
|
|
28
28
|
return true;
|
|
29
29
|
}
|
|
30
30
|
catch {
|
|
@@ -59,6 +59,7 @@ export function probeClaudeAuthenticated() {
|
|
|
59
59
|
timeout: 60_000,
|
|
60
60
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
61
61
|
encoding: 'utf-8',
|
|
62
|
+
windowsHide: true,
|
|
62
63
|
});
|
|
63
64
|
return typeof out === 'string' && out.trim().length > 0;
|
|
64
65
|
}
|
|
@@ -77,7 +78,7 @@ export function ensureClaudeInstalled(opts = {}) {
|
|
|
77
78
|
return { installed: true, alreadyPresent: true };
|
|
78
79
|
const { installArgv } = claudeInstallPlan();
|
|
79
80
|
try {
|
|
80
|
-
execSync(installArgv.join(' '), { stdio: opts.quiet ? 'ignore' : 'inherit' });
|
|
81
|
+
execSync(installArgv.join(' '), { stdio: opts.quiet ? 'ignore' : 'inherit', windowsHide: true });
|
|
81
82
|
}
|
|
82
83
|
catch {
|
|
83
84
|
// Fall through to a definitive PATH re-check rather than trusting the throw.
|
package/dist/commands/add.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Command } from 'commander';
|
|
|
5
5
|
import { post } from '../api.js';
|
|
6
6
|
import { requireConfig } from '../config.js';
|
|
7
7
|
import { sync } from '../sync.js';
|
|
8
|
-
import { success, muted, bold } from '../colors.js';
|
|
8
|
+
import { success, muted, bold, warning } from '../colors.js';
|
|
9
9
|
import { run } from '../helpers/index.js';
|
|
10
10
|
import { createProgressReporter, withSpinner } from '../progress.js';
|
|
11
11
|
const STARTERS = [
|
|
@@ -144,7 +144,7 @@ function buildLocalPayload(rootDir) {
|
|
|
144
144
|
return { name: path.basename(rootDir), files };
|
|
145
145
|
}
|
|
146
146
|
export const addCommand = new Command('add')
|
|
147
|
-
.description('Add a template
|
|
147
|
+
.description('Add a template or kit')
|
|
148
148
|
.argument('[name]', 'Template/kit key, OR a local directory path (./, ~/, or /abs). Omit for help; use --list for just the catalog.')
|
|
149
149
|
.option('--title <title>', 'App title - templates only (defaults to project name)')
|
|
150
150
|
.option('--description <desc>', 'App description for meta tags - templates only')
|
|
@@ -225,6 +225,15 @@ export const addCommand = new Command('add')
|
|
|
225
225
|
for (const n of data.notes)
|
|
226
226
|
console.log(n);
|
|
227
227
|
}
|
|
228
|
+
// Partial install (server HTTP 207): some files were dropped. Surface them so
|
|
229
|
+
// the user knows the install is incomplete rather than reading a clean success.
|
|
230
|
+
if (data.failed?.length) {
|
|
231
|
+
console.log('');
|
|
232
|
+
console.warn(warning(`${data.failed.length} file(s) failed to install:`));
|
|
233
|
+
for (const f of data.failed)
|
|
234
|
+
console.warn(warning(` ! ${f.path}: ${f.error}`));
|
|
235
|
+
console.warn(warning(`Re-run \`gipity add ${data.kit ?? name}${opts.force ? ' --force' : ''}\` to retry the dropped files.`));
|
|
236
|
+
}
|
|
228
237
|
// After scaffolding an app, point at kits as the next step - they add
|
|
229
238
|
// features (multiplayer, etc.) into the app you just created.
|
|
230
239
|
if (data.kind !== 'kit' && KITS.length > 0) {
|
|
@@ -21,7 +21,7 @@ function keyToIndex(key) {
|
|
|
21
21
|
return code >= 0 && code < 26 ? code : -1;
|
|
22
22
|
}
|
|
23
23
|
export const approvalCommand = new Command('approval')
|
|
24
|
-
.description('
|
|
24
|
+
.description('Manage approvals');
|
|
25
25
|
approvalCommand
|
|
26
26
|
.command('list')
|
|
27
27
|
.description('List pending approvals')
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { get, post } from '../api.js';
|
|
3
|
+
import { resolveProjectContext } from '../config.js';
|
|
4
|
+
import { bold, muted, success, warning } from '../colors.js';
|
|
5
|
+
import { run } from '../helpers/index.js';
|
|
6
|
+
// Kept in sync with @easyclaw/shared BUG_REPORT_CATEGORIES / _SEVERITIES (the CLI
|
|
7
|
+
// package can't import the server's shared package). The server validates
|
|
8
|
+
// authoritatively; these are for --help and a friendly client-side check.
|
|
9
|
+
const CATEGORIES = ['cli', 'deploy', 'template', 'kit', 'db', 'docs', 'skill', 'service', 'sandbox', 'other'];
|
|
10
|
+
const SEVERITIES = ['S1', 'S2', 'S3', 'S4'];
|
|
11
|
+
const SEVERITY_HINT = 'S1=blocker, S2=major, S3=minor, S4=friction';
|
|
12
|
+
export const bugCommand = new Command('bug')
|
|
13
|
+
.description('Report Gipity platform bugs / friction in real time')
|
|
14
|
+
.addHelpText('after', `\nCapture platform friction the moment you hit it — even one you worked around —` +
|
|
15
|
+
`\nso the team can triage it into a fix.\n` +
|
|
16
|
+
`\nCategories: ${CATEGORIES.join(', ')}` +
|
|
17
|
+
`\nSeverity: ${SEVERITY_HINT}` +
|
|
18
|
+
`\nNever include PII or user data — describe the platform problem in the abstract.`);
|
|
19
|
+
bugCommand
|
|
20
|
+
.command('report')
|
|
21
|
+
.description('File a bug / friction report about the Gipity platform')
|
|
22
|
+
.requiredOption('--category <cat>', `One of: ${CATEGORIES.join(', ')}`)
|
|
23
|
+
.requiredOption('--severity <sev>', SEVERITY_HINT)
|
|
24
|
+
.requiredOption('--summary <text>', 'One line, 7 words max. No PII/user data.')
|
|
25
|
+
.option('--detail <text>', 'Succinct: what you did, what failed, the workaround. No PII/user data.')
|
|
26
|
+
.option('--project <guid-or-slug>', 'Attribute to a specific project instead of cwd / Home')
|
|
27
|
+
.option('--json', 'Output raw JSON')
|
|
28
|
+
.action((opts) => run('Bug report', async () => {
|
|
29
|
+
const category = String(opts.category).toLowerCase();
|
|
30
|
+
const severity = /^[1-4]$/.test(opts.severity) ? `S${opts.severity}` : String(opts.severity).toUpperCase();
|
|
31
|
+
if (!CATEGORIES.includes(category))
|
|
32
|
+
throw new Error(`--category must be one of: ${CATEGORIES.join(', ')}`);
|
|
33
|
+
if (!SEVERITIES.includes(severity))
|
|
34
|
+
throw new Error(`--severity must be one of: ${SEVERITY_HINT}`);
|
|
35
|
+
if (String(opts.summary).trim().split(/\s+/).filter(Boolean).length > 7) {
|
|
36
|
+
throw new Error('--summary must be 7 words or fewer');
|
|
37
|
+
}
|
|
38
|
+
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
39
|
+
const res = await post(`/api/${config.projectGuid}/services/bug-report/submit`, { category, severity, summary: opts.summary, detail: opts.detail });
|
|
40
|
+
if (opts.json) {
|
|
41
|
+
console.log(JSON.stringify(res.data));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
console.log(success(`✓ Bug report filed (${res.data.report_guid}) — queued for triage.`));
|
|
45
|
+
}));
|
|
46
|
+
bugCommand
|
|
47
|
+
.command('list')
|
|
48
|
+
.description('List the bug / friction reports you have filed')
|
|
49
|
+
.option('--project <guid-or-slug>', 'Resolve auth against a specific project instead of cwd / Home')
|
|
50
|
+
.option('--json', 'Output raw JSON')
|
|
51
|
+
.action((opts) => run('Bug report', async () => {
|
|
52
|
+
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
53
|
+
const res = await get(`/api/${config.projectGuid}/services/bug-report/list`);
|
|
54
|
+
if (opts.json) {
|
|
55
|
+
console.log(JSON.stringify(res.data));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const rows = res.data;
|
|
59
|
+
if (!rows.length) {
|
|
60
|
+
console.log(warning('No bug reports filed yet.'));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
console.log(bold(`${rows.length} report${rows.length === 1 ? '' : 's'}`));
|
|
64
|
+
for (const r of rows) {
|
|
65
|
+
console.log(` ${r.severity} ${muted(r.category.padEnd(8))} ${r.summary} ${muted(`[${r.status}]`)}`);
|
|
66
|
+
console.log(muted(` ${r.report_guid} ${r.created_at}`));
|
|
67
|
+
}
|
|
68
|
+
}));
|
|
69
|
+
//# sourceMappingURL=bug.js.map
|
package/dist/commands/credits.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import {
|
|
3
|
-
import { get } from '../api.js';
|
|
4
|
-
import { brand, muted } from '../colors.js';
|
|
2
|
+
import { spawnCommand } from '../platform.js';
|
|
3
|
+
import { get, post, ApiError } from '../api.js';
|
|
4
|
+
import { brand, dim, bold, muted, success } from '../colors.js';
|
|
5
5
|
import { run, printList } from '../helpers/index.js';
|
|
6
6
|
const PRICING_URL = 'https://prompt.gipity.ai/pricing';
|
|
7
7
|
function openInBrowser(url) {
|
|
@@ -12,7 +12,7 @@ function openInBrowser(url) {
|
|
|
12
12
|
process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]] :
|
|
13
13
|
['xdg-open', [url]];
|
|
14
14
|
try {
|
|
15
|
-
const child =
|
|
15
|
+
const child = spawnCommand(cmd, args, { stdio: 'ignore', detached: true });
|
|
16
16
|
// A missing/non-executable launcher (ENOENT, or EACCES on WSL/minimal Linux
|
|
17
17
|
// where xdg-open isn't runnable) is reported ASYNCHRONOUSLY via an 'error'
|
|
18
18
|
// event - not a throw - so the try/catch alone can't stop it. With no
|
|
@@ -25,18 +25,92 @@ function openInBrowser(url) {
|
|
|
25
25
|
// Synchronous spawn failure - caller prints the URL.
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
// ---- Plan limits formatting (folded in from the old `plan` command) ----
|
|
29
|
+
// Every key the plan definition enforces is rendered, so `credits` /
|
|
30
|
+
// `credits list` are a full-transparency view of what your plan actually caps.
|
|
31
|
+
const LIMIT_LABELS = {
|
|
32
|
+
maxProjects: 'Projects',
|
|
33
|
+
maxDatabases: 'Databases',
|
|
34
|
+
storageQuotaBytes: 'Storage',
|
|
35
|
+
maxWorkflows: 'Workflows',
|
|
36
|
+
minCronIntervalHours: 'Cron frequency',
|
|
37
|
+
maxConcurrentChats: 'Concurrent chats',
|
|
38
|
+
deployRatePerMinute: 'Deploys/min',
|
|
39
|
+
testFileConcurrency: 'Parallel tests',
|
|
40
|
+
};
|
|
41
|
+
// Nested serviceLimits (media generation entitlements). Convention:
|
|
42
|
+
// -1 = unlimited, 0 = Pro-only (blocked on free), N = N free uses/month.
|
|
43
|
+
const SERVICE_LABELS = {
|
|
44
|
+
video: 'Video generation',
|
|
45
|
+
music: 'Music generation',
|
|
46
|
+
image: 'Image generation',
|
|
47
|
+
audio: 'Speech & sound FX',
|
|
48
|
+
};
|
|
49
|
+
function formatLimit(key, value) {
|
|
50
|
+
if (typeof value !== 'number')
|
|
51
|
+
return String(value);
|
|
52
|
+
if (key === 'storageQuotaBytes') {
|
|
53
|
+
const gb = value / (1024 ** 3);
|
|
54
|
+
return `${gb.toFixed(gb >= 1 ? 1 : 2)} GB`;
|
|
55
|
+
}
|
|
56
|
+
if (key === 'minCronIntervalHours') {
|
|
57
|
+
return value === 0 ? 'no minimum' : `${value}h minimum`;
|
|
58
|
+
}
|
|
59
|
+
return value.toLocaleString();
|
|
60
|
+
}
|
|
61
|
+
function formatServiceLimit(value) {
|
|
62
|
+
if (value === -1)
|
|
63
|
+
return 'unlimited';
|
|
64
|
+
if (value === 0)
|
|
65
|
+
return 'Pro only';
|
|
66
|
+
if (typeof value === 'number')
|
|
67
|
+
return `${value.toLocaleString()}/mo free`;
|
|
68
|
+
return String(value);
|
|
69
|
+
}
|
|
70
|
+
function renderLimits(limits, indent = '') {
|
|
71
|
+
for (const key of Object.keys(LIMIT_LABELS)) {
|
|
72
|
+
const value = limits[key];
|
|
73
|
+
if (value !== undefined) {
|
|
74
|
+
console.log(`${indent}${LIMIT_LABELS[key].padEnd(18)} ${formatLimit(key, value)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const services = limits.serviceLimits;
|
|
78
|
+
if (services && typeof services === 'object') {
|
|
79
|
+
console.log(`${indent}Media generation`);
|
|
80
|
+
for (const key of Object.keys(SERVICE_LABELS)) {
|
|
81
|
+
const value = services[key];
|
|
82
|
+
if (value !== undefined) {
|
|
83
|
+
console.log(`${indent} ${SERVICE_LABELS[key].padEnd(16)} ${formatServiceLimit(value)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// `credits` is the single account/billing hub: tier, live balance, the full set
|
|
89
|
+
// of plan limits, and an upgrade nudge. `plan` aliases to it (so old muscle
|
|
90
|
+
// memory resolves to one implementation); compare plans with `plan list`.
|
|
28
91
|
export const creditsCommand = new Command('credits')
|
|
29
|
-
.
|
|
92
|
+
.alias('plan')
|
|
93
|
+
.description('Show your plan, credit balance, and limits')
|
|
30
94
|
.option('--json', 'Output as JSON')
|
|
31
95
|
.action((opts) => run('Credits', async () => {
|
|
96
|
+
// Version retention rides alongside the Storage limit; a failure fetching it
|
|
97
|
+
// must not sink the whole view, so degrade to null and omit the line.
|
|
98
|
+
const fetchRetention = () => get('/users/me/retention').then(r => r.data).catch(() => null);
|
|
32
99
|
if (opts.json) {
|
|
33
|
-
const
|
|
34
|
-
|
|
100
|
+
const [balRes, limitsRes, subRes, retention] = await Promise.all([
|
|
101
|
+
get('/credits/balance'),
|
|
102
|
+
get('/users/me/limits'),
|
|
103
|
+
get('/credits/subscription'),
|
|
104
|
+
fetchRetention(),
|
|
105
|
+
]);
|
|
106
|
+
console.log(JSON.stringify({ subscription: subRes.data, balance: balRes.data, limits: limitsRes.data, retention }));
|
|
35
107
|
return;
|
|
36
108
|
}
|
|
37
|
-
const [balRes, subRes] = await Promise.all([
|
|
109
|
+
const [balRes, subRes, limitsRes, retention] = await Promise.all([
|
|
38
110
|
get('/credits/balance'),
|
|
39
111
|
get('/credits/subscription'),
|
|
112
|
+
get('/users/me/limits'),
|
|
113
|
+
fetchRetention(),
|
|
40
114
|
]);
|
|
41
115
|
const sub = subRes.data;
|
|
42
116
|
const tierLabel = sub.tier === 'pro' ? 'Gipity Pro' : 'Free';
|
|
@@ -45,17 +119,170 @@ export const creditsCommand = new Command('credits')
|
|
|
45
119
|
if (balRes.data.balances.length > 0) {
|
|
46
120
|
for (const b of balRes.data.balances) {
|
|
47
121
|
const exp = new Date(b.expiresAt).toLocaleDateString();
|
|
48
|
-
console.log(
|
|
122
|
+
console.log(` ${b.source}: ${b.creditsRemaining.toLocaleString()} ${muted(`expires ${exp}`)}`);
|
|
49
123
|
}
|
|
50
124
|
}
|
|
125
|
+
console.log('\nLimits:');
|
|
126
|
+
renderLimits(limitsRes.data.limits, ' ');
|
|
127
|
+
if (retention) {
|
|
128
|
+
console.log(` ${'Version retention'.padEnd(18)} ${retention.days} days / ${retention.count} copies`);
|
|
129
|
+
}
|
|
130
|
+
console.log('');
|
|
131
|
+
if (sub.tier !== 'pro') {
|
|
132
|
+
console.log(dim('Upgrade to Gipity Pro for higher limits and 20,000 credits/mo — run `gipity credits buy`.'));
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log(dim('Need more credits? Run `gipity credits list` to see credit packs, then `gipity credits buy <pack>`.'));
|
|
136
|
+
}
|
|
51
137
|
}));
|
|
138
|
+
// `credits list` compares every plan (with full limits) and the available
|
|
139
|
+
// credit packs — the "what can I upgrade to" view.
|
|
52
140
|
creditsCommand
|
|
53
|
-
.command('
|
|
54
|
-
.description('
|
|
55
|
-
.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
141
|
+
.command('list')
|
|
142
|
+
.description('Compare all plans and credit packs')
|
|
143
|
+
.option('--json', 'Output as JSON')
|
|
144
|
+
// optsWithGlobals(): the parent `credits` command also declares --json, which
|
|
145
|
+
// commander binds to the parent - read merged opts so --json is seen here.
|
|
146
|
+
.action((_opts, cmd) => run('Plans', async () => {
|
|
147
|
+
const opts = cmd.optsWithGlobals();
|
|
148
|
+
const [plansRes, productsRes, subRes] = await Promise.all([
|
|
149
|
+
get('/plans'),
|
|
150
|
+
get('/credits/products'),
|
|
151
|
+
get('/credits/subscription'),
|
|
152
|
+
]);
|
|
153
|
+
if (opts.json) {
|
|
154
|
+
console.log(JSON.stringify({ currentTier: subRes.data.tier, plans: plansRes.data, products: productsRes.data }));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const currentTier = subRes.data.tier;
|
|
158
|
+
const plans = plansRes.data;
|
|
159
|
+
if (plans.length === 0) {
|
|
160
|
+
console.log('No active plans.');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
console.log(bold('Plans'));
|
|
164
|
+
for (const plan of plans) {
|
|
165
|
+
const marker = plan.tier === currentTier ? brand('* ') : ' ';
|
|
166
|
+
const price = plan.monthlyPriceUsd > 0 ? ` - $${plan.monthlyPriceUsd}/mo` : ' - Free';
|
|
167
|
+
const credits = plan.monthlyCredits > 0
|
|
168
|
+
? ` - ${plan.monthlyCredits.toLocaleString()} credits/mo (${plan.creditExpiryDays}-day expiry)`
|
|
169
|
+
: '';
|
|
170
|
+
console.log(`${marker}${plan.displayName} (${plan.tier})${price}${credits}`);
|
|
171
|
+
renderLimits(plan.limits, ' ');
|
|
172
|
+
console.log('');
|
|
173
|
+
}
|
|
174
|
+
const packs = productsRes.data.filter(p => p.type === 'one_time');
|
|
175
|
+
if (packs.length > 0) {
|
|
176
|
+
console.log(bold('Credit packs') + muted(' (require an active Pro subscription)'));
|
|
177
|
+
for (const pack of packs) {
|
|
178
|
+
const lock = pack.available ? '' : muted(' [Pro only]');
|
|
179
|
+
console.log(` ${pack.name} - $${pack.amountUsd} - ${pack.credits.toLocaleString()} credits${lock}`);
|
|
180
|
+
}
|
|
181
|
+
console.log('');
|
|
182
|
+
}
|
|
183
|
+
console.log(dim('* = your current plan. Upgrade or top up with `gipity credits buy [pro|<pack-credits>]`.'));
|
|
184
|
+
}));
|
|
185
|
+
// `credits buy` is the ONE purchase command. It resolves the target to a
|
|
186
|
+
// Stripe price and prints a checkout link (agent-friendly: the link is always
|
|
187
|
+
// printed so it can be relayed to the user; --open also launches a browser).
|
|
188
|
+
creditsCommand
|
|
189
|
+
.command('buy [target]')
|
|
190
|
+
.alias('upgrade')
|
|
191
|
+
.description('Upgrade your plan or buy a credit pack (target: a tier like "pro", or a pack credit amount)')
|
|
192
|
+
.option('--open', 'Open the checkout page in your browser')
|
|
193
|
+
.option('--json', 'Output the checkout URL as JSON')
|
|
194
|
+
// optsWithGlobals(): the parent `credits` command also declares --json, which
|
|
195
|
+
// commander binds to the parent - read merged opts so --json/--open are seen here.
|
|
196
|
+
.action((target, _opts, cmd) => run('Buy', async () => {
|
|
197
|
+
const opts = cmd.optsWithGlobals();
|
|
198
|
+
const [productsRes, subRes] = await Promise.all([
|
|
199
|
+
get('/credits/products'),
|
|
200
|
+
get('/credits/subscription'),
|
|
201
|
+
]);
|
|
202
|
+
const products = productsRes.data;
|
|
203
|
+
const currentTier = subRes.data.tier;
|
|
204
|
+
// Resolve the target product. With no arg, default to the first available
|
|
205
|
+
// paid subscription (the upgrade path). With an arg, match a plan/pack by
|
|
206
|
+
// name or credit amount, mirroring the cloud agent's product keys.
|
|
207
|
+
// Non-happy exits must stay parseable under --json (agents rely on it), so
|
|
208
|
+
// emit a JSON { error } instead of prose when the flag is set.
|
|
209
|
+
const bail = (message, humanLines) => {
|
|
210
|
+
if (opts.json)
|
|
211
|
+
console.log(JSON.stringify({ error: message }));
|
|
212
|
+
else
|
|
213
|
+
for (const line of humanLines)
|
|
214
|
+
console.log(line);
|
|
215
|
+
};
|
|
216
|
+
let product;
|
|
217
|
+
if (!target) {
|
|
218
|
+
product = products.find(p => p.type === 'subscription' && p.available);
|
|
219
|
+
if (!product) {
|
|
220
|
+
bail(currentTier === 'pro'
|
|
221
|
+
? "You're already on Gipity Pro. To top up credits, run `gipity credits list` and buy a pack, e.g. `gipity credits buy 20000`."
|
|
222
|
+
: 'No upgrade plan is available right now.', [
|
|
223
|
+
currentTier === 'pro'
|
|
224
|
+
? `You're already on ${brand('Gipity Pro')}. To top up credits, run \`gipity credits list\` and buy a pack, e.g. \`gipity credits buy 20000\`.`
|
|
225
|
+
: 'No upgrade plan is available right now.',
|
|
226
|
+
dim(`Browse everything at ${PRICING_URL}`),
|
|
227
|
+
]);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
const key = target.toLowerCase();
|
|
233
|
+
product = products.find(p => p.type === 'subscription' && p.name.toLowerCase() === key)
|
|
234
|
+
// pack keyed by credit amount, e.g. "20000" - MUST be a pack, not a
|
|
235
|
+
// subscription (Pro's monthly_credits can equal a pack's credit count).
|
|
236
|
+
?? products.find(p => p.type === 'one_time' && String(p.credits) === key)
|
|
237
|
+
// accept "pro" against the single paid plan when only one exists
|
|
238
|
+
?? (key === 'pro' ? products.find(p => p.type === 'subscription') : undefined);
|
|
239
|
+
if (!product) {
|
|
240
|
+
bail(`Unknown option "${target}". Run \`gipity credits list\` to see plans and packs.`, [`Unknown option "${target}". Run \`gipity credits list\` to see plans and packs.`]);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (!product.available) {
|
|
244
|
+
bail(product.type === 'subscription'
|
|
245
|
+
? `You're already on ${product.name}.`
|
|
246
|
+
: 'Credit packs require an active Pro subscription. Run `gipity credits buy pro` first.', [product.type === 'subscription'
|
|
247
|
+
? `You're already on ${brand(product.name)}.`
|
|
248
|
+
: 'Credit packs require an active Pro subscription. Run `gipity credits buy pro` first.']);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const res = await post('/credits/purchase', { priceId: product.priceId });
|
|
254
|
+
const url = res.data.checkoutUrl;
|
|
255
|
+
if (opts.json) {
|
|
256
|
+
console.log(JSON.stringify({ product: product.name, amountUsd: product.amountUsd, checkoutUrl: url }));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const verb = product.type === 'subscription' ? 'Upgrading to' : 'Buying';
|
|
260
|
+
const per = product.type === 'subscription' ? '/mo' : '';
|
|
261
|
+
console.log(`${verb} ${brand(product.name)} ${muted(`($${product.amountUsd}${per}, ${product.credits.toLocaleString()} credits)`)}`);
|
|
262
|
+
console.log('');
|
|
263
|
+
console.log(` ${bold('Checkout:')} ${success(url)}`);
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log(dim('Open the link to complete your purchase — 2 minutes, cancel anytime. Your plan unlocks the moment payment clears.'));
|
|
266
|
+
if (opts.open)
|
|
267
|
+
openInBrowser(url);
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
// Payments not configured / Stripe hiccup: fall back to the pricing page
|
|
271
|
+
// so the user is never dead-ended.
|
|
272
|
+
if (err instanceof ApiError) {
|
|
273
|
+
if (opts.json) {
|
|
274
|
+
console.log(JSON.stringify({ error: err.message, checkoutUrl: null, fallbackUrl: PRICING_URL }));
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
console.log(muted(`Couldn't create a direct checkout link (${err.message}).`));
|
|
278
|
+
console.log(`Complete your purchase at ${brand(PRICING_URL)}`);
|
|
279
|
+
}
|
|
280
|
+
if (opts.open)
|
|
281
|
+
openInBrowser(PRICING_URL);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
throw err;
|
|
285
|
+
}
|
|
59
286
|
}));
|
|
60
287
|
creditsCommand
|
|
61
288
|
.command('usage')
|
package/dist/commands/email.js
CHANGED
|
@@ -7,7 +7,8 @@ function collect(value, prev) {
|
|
|
7
7
|
return [...prev, value];
|
|
8
8
|
}
|
|
9
9
|
export const emailCommand = new Command('email')
|
|
10
|
-
.description('Send email
|
|
10
|
+
.description('Send email')
|
|
11
|
+
.addHelpText('after', '\nSends from gipity@gipity.ai. Omit --to to self-send.')
|
|
11
12
|
.requiredOption('--subject <subject>', 'Email subject')
|
|
12
13
|
.requiredOption('--body <body>', 'Email body (plain text)')
|
|
13
14
|
.option('--to <email>', 'Recipient (repeatable; omit for self-send)', collect, [])
|
package/dist/commands/gmail.js
CHANGED
|
@@ -3,7 +3,8 @@ import { get, post } from '../api.js';
|
|
|
3
3
|
import { run, printList, printResult } from '../helpers/index.js';
|
|
4
4
|
import { muted } from '../colors.js';
|
|
5
5
|
export const gmailCommand = new Command('gmail')
|
|
6
|
-
.description('
|
|
6
|
+
.description('Use your Gmail')
|
|
7
|
+
.addHelpText('after', '\nSend, reply, search, or read via your own Gmail (separate from `gipity email`).');
|
|
7
8
|
gmailCommand
|
|
8
9
|
.command('search <query...>')
|
|
9
10
|
.description('Search your Gmail (standard Gmail search syntax)')
|
package/dist/commands/init.js
CHANGED
|
@@ -22,7 +22,8 @@ function resolveTools(forFlag) {
|
|
|
22
22
|
return SUPPORTED_TOOLS.filter(t => requested.includes(t.key) || (!t.optIn && requested.includes('all')));
|
|
23
23
|
}
|
|
24
24
|
export const initCommand = new Command('init')
|
|
25
|
-
.description('Link this directory to a
|
|
25
|
+
.description('Link this directory to a project')
|
|
26
|
+
.addHelpText('after', '\nWrites CLAUDE.md/AGENTS.md primer files so your AI coding tool understands Gipity.')
|
|
26
27
|
.argument('[name]', 'Project name/slug (defaults to current directory name)')
|
|
27
28
|
.option('--agent <guid>', 'Agent GUID to use')
|
|
28
29
|
.option('--for <tools>', `Which AI tool primer files to write (comma-separated). Default: all except aider (opt-in - it also writes .aider.conf.yml). Choices: ${TOOL_KEYS.join(', ')}, all`)
|
package/dist/commands/job.js
CHANGED
|
@@ -4,7 +4,8 @@ import { requireConfig } from '../config.js';
|
|
|
4
4
|
import { error as clrError, bold, muted, success, warning as warn } from '../colors.js';
|
|
5
5
|
import { run, printList } from '../helpers/index.js';
|
|
6
6
|
export const jobCommand = new Command('job')
|
|
7
|
-
.description('
|
|
7
|
+
.description('Run long jobs (CPU/GPU)')
|
|
8
|
+
.addHelpText('after', '\nCPU sandbox or GPU (gpu-small=L4, gpu-medium=A10G, gpu-large=A100, gpu-huge=H100). Declared in the gipity.yaml jobs: phase; submit and poll via the subcommands below.');
|
|
8
9
|
jobCommand
|
|
9
10
|
.command('list')
|
|
10
11
|
.description('List jobs')
|
|
@@ -192,7 +193,7 @@ jobCommand
|
|
|
192
193
|
.action(async (name, opts) => {
|
|
193
194
|
const { existsSync, statSync } = await import('node:fs');
|
|
194
195
|
const { resolve, join } = await import('node:path');
|
|
195
|
-
const {
|
|
196
|
+
const { spawnCommand, spawnSyncCommand } = await import('../platform.js');
|
|
196
197
|
const jobDir = resolve(process.cwd(), 'jobs', name);
|
|
197
198
|
if (!existsSync(jobDir) || !statSync(jobDir).isDirectory()) {
|
|
198
199
|
console.error(clrError(`jobs/${name}/ not found in current directory`));
|
|
@@ -212,7 +213,7 @@ jobCommand
|
|
|
212
213
|
process.exit(1);
|
|
213
214
|
}
|
|
214
215
|
// Confirm docker is reachable before bothering to build args.
|
|
215
|
-
const probe =
|
|
216
|
+
const probe = spawnSyncCommand('docker', ['info'], { stdio: 'ignore' });
|
|
216
217
|
if (probe.status !== 0) {
|
|
217
218
|
console.error(clrError('docker daemon not reachable. Start Docker Desktop / dockerd and retry.'));
|
|
218
219
|
process.exit(1);
|
|
@@ -239,7 +240,7 @@ jobCommand
|
|
|
239
240
|
shellCmd = `echo '[run-local] running ${handlerCmd} (${picked.runtime})' >&2 && exec ${handlerCmd}`;
|
|
240
241
|
}
|
|
241
242
|
const runArgs = ['run', ...sharedArgs, '--entrypoint', 'sh', opts.image, '-c', shellCmd];
|
|
242
|
-
const child =
|
|
243
|
+
const child = spawnCommand('docker', runArgs, { stdio: 'inherit' });
|
|
243
244
|
// A missing `docker` binary is reported asynchronously via 'error' (not a
|
|
244
245
|
// throw), so a try/catch can't stop it - without this handler Node crashes
|
|
245
246
|
// with a raw stack trace instead of a clear message.
|
package/dist/commands/login.js
CHANGED
package/dist/commands/notify.js
CHANGED
|
@@ -4,7 +4,8 @@ import { resolveProjectContext } from '../config.js';
|
|
|
4
4
|
import { bold, muted, success, warning } from '../colors.js';
|
|
5
5
|
import { run } from '../helpers/index.js';
|
|
6
6
|
export const notifyCommand = new Command('notify')
|
|
7
|
-
.description('
|
|
7
|
+
.description('Web push notifications')
|
|
8
|
+
.addHelpText('after', '\nGipity Notify: send a test notification or inspect subscriptions.');
|
|
8
9
|
notifyCommand
|
|
9
10
|
.command('test')
|
|
10
11
|
.description('Send a test push notification to yourself, a user, or everyone')
|
package/dist/commands/page.js
CHANGED
|
@@ -10,7 +10,7 @@ import { pageFetchCommand } from './page-fetch.js';
|
|
|
10
10
|
// top-level surface lean and makes the siblings discoverable via `page --help`.
|
|
11
11
|
// `inspect` is the rendered DOM (browser); `fetch` is the raw asset (plain HTTP).
|
|
12
12
|
export const pageCommand = new Command('page')
|
|
13
|
-
.description('Inspect
|
|
13
|
+
.description('Inspect web pages')
|
|
14
14
|
.addCommand(pageInspectCommand)
|
|
15
15
|
.addCommand(pageEvalCommand)
|
|
16
16
|
.addCommand(pageScreenshotCommand)
|
|
@@ -19,7 +19,8 @@ function renderStatus(status) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
export const paymentsCommand = new Command('payments')
|
|
22
|
-
.description('
|
|
22
|
+
.description('Charge your users via Stripe')
|
|
23
|
+
.addHelpText('after', '\nAccept one-time purchases and subscriptions. Set up with `gipity payments connect`.');
|
|
23
24
|
paymentsCommand
|
|
24
25
|
.command('connect')
|
|
25
26
|
.description('Start (or resume) Stripe onboarding for this app — prints a link to finish in your browser')
|
package/dist/commands/plan.js
CHANGED
|
@@ -31,7 +31,7 @@ function renderLimits(limits, indent = '') {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
export const planCommand = new Command('plan')
|
|
34
|
-
.description('Show your
|
|
34
|
+
.description('Show your plan')
|
|
35
35
|
.option('--json', 'Output as JSON')
|
|
36
36
|
.action((opts) => run('Plan', async () => {
|
|
37
37
|
const [limitsRes, plansRes] = await Promise.all([
|
package/dist/commands/push.js
CHANGED
|
@@ -11,9 +11,13 @@ export const pushCommand = new Command('push')
|
|
|
11
11
|
try {
|
|
12
12
|
const fullPath = resolve(file);
|
|
13
13
|
if (opts.background) {
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
// Detach a background `gipity push` and exit immediately. Goes through
|
|
15
|
+
// spawnCommand (Node binary running our own entry script - no IPC
|
|
16
|
+
// channel is needed, so spawn beats fork), which defaults
|
|
17
|
+
// `windowsHide: true` so the detached child never flashes a console
|
|
18
|
+
// window on Windows.
|
|
19
|
+
const { spawnCommand } = await import('../platform.js');
|
|
20
|
+
const child = spawnCommand(process.execPath, [process.argv[1], 'push', fullPath, '--quiet'], {
|
|
17
21
|
detached: true,
|
|
18
22
|
stdio: 'ignore',
|
|
19
23
|
});
|
package/dist/commands/relay.js
CHANGED
|
@@ -14,7 +14,7 @@ import { bold, brand, dim, success, error as clrError, muted, } from '../colors.
|
|
|
14
14
|
import * as state from '../relay/state.js';
|
|
15
15
|
import * as daemon from '../relay/daemon.js';
|
|
16
16
|
import { UnsupportedPlatformError } from '../relay/installers.js';
|
|
17
|
-
import { pairDevice, startDaemon, installAutostart } from '../relay/setup.js';
|
|
17
|
+
import { pairDevice, startDaemon, installAutostart, removeAutostart } from '../relay/setup.js';
|
|
18
18
|
import { registerInstallCommands } from './relay-install.js';
|
|
19
19
|
export const relayCommand = new Command('relay')
|
|
20
20
|
.description('Pair with the web CLI');
|
|
@@ -269,8 +269,32 @@ relayCommand
|
|
|
269
269
|
console.error(muted('Local token cleared anyway. Visit the web CLI to confirm the server-side revoke.'));
|
|
270
270
|
}
|
|
271
271
|
state.clearDevice();
|
|
272
|
+
// Remove the OS autostart unit too. Otherwise the login service relaunches
|
|
273
|
+
// `relay run`, which - finding no device but a valid login - silently
|
|
274
|
+
// re-registers a NEW device, undoing this revoke (on macOS the old
|
|
275
|
+
// KeepAlive=true even relaunched on the clean exit). Best-effort: an
|
|
276
|
+
// unsupported OS or a missing unit is fine.
|
|
277
|
+
let autostartRemoved = false;
|
|
278
|
+
try {
|
|
279
|
+
autostartRemoved = removeAutostart().ok;
|
|
280
|
+
}
|
|
281
|
+
catch { /* unsupported platform / no unit - ignore */ }
|
|
282
|
+
// Stop the currently-running daemon now rather than waiting for it to
|
|
283
|
+
// notice via a 401 on its next poll (~up to a hold cycle).
|
|
284
|
+
const pidPath = state.getDaemonPidPath();
|
|
285
|
+
if (existsSync(pidPath)) {
|
|
286
|
+
const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
|
|
287
|
+
if (pid && !isNaN(pid)) {
|
|
288
|
+
try {
|
|
289
|
+
process.kill(pid, 'SIGTERM');
|
|
290
|
+
}
|
|
291
|
+
catch { /* already gone */ }
|
|
292
|
+
}
|
|
293
|
+
}
|
|
272
294
|
console.log(success('Device revoked + local state cleared.'));
|
|
273
|
-
console.log(muted(
|
|
295
|
+
console.log(muted(autostartRemoved
|
|
296
|
+
? 'Auto-start removed and the background service was signalled to stop.'
|
|
297
|
+
: 'The background service was signalled to stop.'));
|
|
274
298
|
});
|
|
275
299
|
// ─── gipity relay log ──────────────────────────────────────────────────
|
|
276
300
|
relayCommand
|