gipity 1.0.422 → 1.0.424
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.js +8 -3
- package/dist/commands/claude.js +20 -2
- package/dist/commands/deploy.js +29 -1
- package/dist/commands/page-eval.js +12 -2
- package/dist/commands/page-inspect.js +200 -185
- package/dist/commands/push.js +12 -7
- package/dist/commands/sandbox.js +55 -14
- package/dist/commands/test.js +13 -0
- package/dist/helpers/sync.js +8 -1
- package/dist/index.js +19930 -386
- package/dist/knowledge.js +6 -5
- package/dist/sync.js +29 -0
- package/dist/updater/check.js +159 -84
- package/dist/updater/shim.js +153 -71
- package/package.json +5 -3
package/dist/commands/push.js
CHANGED
|
@@ -3,13 +3,13 @@ import { resolve } from 'path';
|
|
|
3
3
|
import { pushFile } from '../sync.js';
|
|
4
4
|
import { error as clrError, success } from '../colors.js';
|
|
5
5
|
export const pushCommand = new Command('push')
|
|
6
|
-
.description('Push
|
|
7
|
-
.argument('<
|
|
6
|
+
.description('Push one or more files')
|
|
7
|
+
.argument('<files...>', 'File path(s) to push')
|
|
8
8
|
.option('--quiet', 'Suppress output')
|
|
9
9
|
.option('--background', 'Fork and exit immediately')
|
|
10
|
-
.action(async (
|
|
10
|
+
.action(async (files, opts) => {
|
|
11
11
|
try {
|
|
12
|
-
const
|
|
12
|
+
const fullPaths = files.map(f => resolve(f));
|
|
13
13
|
if (opts.background) {
|
|
14
14
|
// Detach a background `gipity push` and exit immediately. Goes through
|
|
15
15
|
// spawnCommand (Node binary running our own entry script - no IPC
|
|
@@ -17,16 +17,21 @@ export const pushCommand = new Command('push')
|
|
|
17
17
|
// `windowsHide: true` so the detached child never flashes a console
|
|
18
18
|
// window on Windows.
|
|
19
19
|
const { spawnCommand } = await import('../platform.js');
|
|
20
|
-
const child = spawnCommand(process.execPath, [process.argv[1], 'push',
|
|
20
|
+
const child = spawnCommand(process.execPath, [process.argv[1], 'push', ...fullPaths, '--quiet'], {
|
|
21
21
|
detached: true,
|
|
22
22
|
stdio: 'ignore',
|
|
23
23
|
});
|
|
24
24
|
child.unref();
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
// Sequential on purpose: pushFile takes the per-project sync lock, so
|
|
28
|
+
// parallel pushes would just contend on it. One process pushing N files
|
|
29
|
+
// still beats the old N processes × N lock acquisitions.
|
|
30
|
+
for (const fullPath of fullPaths) {
|
|
31
|
+
await pushFile(fullPath);
|
|
32
|
+
}
|
|
28
33
|
if (!opts.quiet) {
|
|
29
|
-
console.log(success(`Pushed ${
|
|
34
|
+
console.log(success(files.length === 1 ? `Pushed ${files[0]}` : `Pushed ${files.length} files`));
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
catch (err) {
|
package/dist/commands/sandbox.js
CHANGED
|
@@ -35,19 +35,57 @@ const PIN_LANGUAGE_HELP = [
|
|
|
35
35
|
' gipity sandbox run --language bash "<code>" # explicit flag (js | py | bash)',
|
|
36
36
|
' gipity sandbox run --file script.sh # inferred from the file extension',
|
|
37
37
|
].join('\n');
|
|
38
|
+
// Words that open a code statement, not a command line. A single positional
|
|
39
|
+
// starting with one of these is a JS/Python snippet whose language we must not
|
|
40
|
+
// guess (see resolveLanguage) - everything here is either invalid in bash or,
|
|
41
|
+
// worse, VALID in bash with a different meaning (`export FOO=1`, `for ...`).
|
|
42
|
+
const CODE_OPENERS = new Set([
|
|
43
|
+
'const', 'let', 'var', 'function', 'async', 'await', 'import', 'export',
|
|
44
|
+
'console', 'require', 'print', 'def', 'class', 'lambda', 'from', 'return',
|
|
45
|
+
'for', 'while', 'if', 'try', 'with',
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* True when a bare inline string is unmistakably a shell COMMAND LINE
|
|
49
|
+
* (`node tests/game.test.js`, `ls -la`, `ffmpeg -i in.mp4 out.gif`) rather
|
|
50
|
+
* than a code snippet. Deliberately conservative: one line, starts with a
|
|
51
|
+
* plain command word (no parens/quotes/operators in it), not a code opener,
|
|
52
|
+
* not an assignment (`x = 1` is Python-or-bash-ambiguous). Anything that
|
|
53
|
+
* fails these checks still goes through the explicit-pin error below.
|
|
54
|
+
*/
|
|
55
|
+
export function looksLikeShellCommand(code) {
|
|
56
|
+
if (code.includes('\n'))
|
|
57
|
+
return false;
|
|
58
|
+
const m = /^([A-Za-z0-9_.~/-]+)(\s|$)/.exec(code.trim());
|
|
59
|
+
if (!m)
|
|
60
|
+
return false;
|
|
61
|
+
const token = m[1].toLowerCase();
|
|
62
|
+
if (CODE_OPENERS.has(token))
|
|
63
|
+
return false;
|
|
64
|
+
if (/^\s*[A-Za-z_][A-Za-z0-9_]*\s*=/.test(code))
|
|
65
|
+
return false; // assignment
|
|
66
|
+
// A lone bare word (`foo`) is as likely a typo as a command; require either
|
|
67
|
+
// arguments after the word or a path-shaped word (`./build.sh`, `bin/run`).
|
|
68
|
+
return /\s\S/.test(code.trim()) || token.includes('/');
|
|
69
|
+
}
|
|
38
70
|
/**
|
|
39
71
|
* Resolve the language from an explicit signal, or exit.
|
|
40
72
|
*
|
|
41
|
-
* Precedence: interpreter token > --language > --file extension
|
|
73
|
+
* Precedence: interpreter token > --language > --file extension > the
|
|
74
|
+
* command-line heuristic above (bash).
|
|
42
75
|
*
|
|
43
|
-
*
|
|
44
|
-
* snippets parse as more than one of them (`x = 1`, `a[0]`, `foo()`),
|
|
45
|
-
* default silently runs some fraction of input in the wrong
|
|
46
|
-
* behavior defaulted to JavaScript, which meant a shell
|
|
47
|
-
* died with a Node `SyntaxError` at `/work/_run.js` -
|
|
48
|
-
* sync and a server round trip.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
76
|
+
* Beyond that there is no default. js/python/bash are mutually exclusive, and
|
|
77
|
+
* plenty of snippets parse as more than one of them (`x = 1`, `a[0]`, `foo()`),
|
|
78
|
+
* so a blanket default silently runs some fraction of input in the wrong
|
|
79
|
+
* interpreter. The old behavior defaulted to JavaScript, which meant a shell
|
|
80
|
+
* one-liner ran as JS and died with a Node `SyntaxError` at `/work/_run.js` -
|
|
81
|
+
* after paying for a project sync and a server round trip. The one shape we DO
|
|
82
|
+
* default is the unambiguous command line (`gipity sandbox run "node
|
|
83
|
+
* tests/game.test.js"`): it was the single most common rejected invocation,
|
|
84
|
+
* the CLI had everything it needed to run it, and no code snippet matches the
|
|
85
|
+
* heuristic. Ambiguous snippets still fail here, costing nothing and saying
|
|
86
|
+
* what to type. (`docs/skills/sandbox-tools.md` used to carry a hand-written
|
|
87
|
+
* "always pin the language" warning to work around this; the CLI enforces it
|
|
88
|
+
* now.)
|
|
51
89
|
*/
|
|
52
90
|
export function resolveLanguage(opts) {
|
|
53
91
|
const explicit = opts.langFromInterp
|
|
@@ -68,6 +106,8 @@ export function resolveLanguage(opts) {
|
|
|
68
106
|
console.error(dim(`Pass --language explicitly:\n gipity sandbox run --language py --file ${opts.filePath}`));
|
|
69
107
|
process.exit(1);
|
|
70
108
|
}
|
|
109
|
+
if (opts.inlineCode && looksLikeShellCommand(opts.inlineCode))
|
|
110
|
+
return 'bash';
|
|
71
111
|
console.error(clrError('No language specified for inline code.'));
|
|
72
112
|
console.error(dim(`Pin it one of three ways:\n${PIN_LANGUAGE_HELP}`));
|
|
73
113
|
process.exit(1);
|
|
@@ -217,11 +257,12 @@ GCC/Rust).
|
|
|
217
257
|
process.exit(1);
|
|
218
258
|
}
|
|
219
259
|
}
|
|
220
|
-
// Language precedence: interpreter token > --language > file extension
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
// server round trip below, so a
|
|
224
|
-
|
|
260
|
+
// Language precedence: interpreter token > --language > file extension >
|
|
261
|
+
// unambiguous-command-line heuristic (bash). resolveLanguage() exits when
|
|
262
|
+
// nothing pins one and the input isn't command-shaped, rather than guessing.
|
|
263
|
+
// This runs BEFORE the project sync and the server round trip below, so a
|
|
264
|
+
// missing language costs nothing but the message.
|
|
265
|
+
const language = resolveLanguage({ langFromInterp, langOpt: opts.language, filePath, inlineCode });
|
|
225
266
|
// Args are good - now it's worth resolving (and announcing) the project.
|
|
226
267
|
const { config } = await resolveProjectContext();
|
|
227
268
|
const timeout = parseInt(opts.timeout, 10);
|
package/dist/commands/test.js
CHANGED
|
@@ -178,6 +178,19 @@ export const testCommand = new Command('test')
|
|
|
178
178
|
console.log(clrError(`Run failed: ${data.errorMessage}`));
|
|
179
179
|
console.log('');
|
|
180
180
|
}
|
|
181
|
+
// Failures recap. Results stream incrementally above, but agents usually
|
|
182
|
+
// read this output through a `tail` window, so anything printed early is
|
|
183
|
+
// lost - restate every failure (name + assertion message) right before
|
|
184
|
+
// the summary, where it's guaranteed to survive truncation from the top.
|
|
185
|
+
if (data.failed > 0) {
|
|
186
|
+
console.log(clrError('Failures:'));
|
|
187
|
+
for (const r of data.results.filter(r => r.status === 'failed')) {
|
|
188
|
+
const where = r.path ? `${r.path}/` : '';
|
|
189
|
+
console.log(` ${statusIcon('failed')} ${where}${r.name || '(unnamed test - the file may have crashed outside a test)'}`);
|
|
190
|
+
console.log(` ${clrError(r.error || `(no assertion message recorded - inspect with: gipity test status ${runGuid} --json)`)}`);
|
|
191
|
+
}
|
|
192
|
+
console.log('');
|
|
193
|
+
}
|
|
181
194
|
// Summary
|
|
182
195
|
const parts = [];
|
|
183
196
|
if (data.passed > 0)
|
package/dist/helpers/sync.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* sync.ts - Shared sync-before-action helper.
|
|
3
3
|
*/
|
|
4
|
-
import { sync } from '../sync.js';
|
|
4
|
+
import { sync, isLocalTreeClean } from '../sync.js';
|
|
5
5
|
import { muted, error as clrError } from '../colors.js';
|
|
6
6
|
import { createProgressReporter } from '../progress.js';
|
|
7
7
|
/**
|
|
@@ -15,6 +15,13 @@ import { createProgressReporter } from '../progress.js';
|
|
|
15
15
|
export async function syncBeforeAction(opts) {
|
|
16
16
|
if (opts.sync === false)
|
|
17
17
|
return;
|
|
18
|
+
// Nothing changed locally since the last sync → nothing to push, and the
|
|
19
|
+
// action (deploy etc.) reads server-side state anyway. Skip the whole sync
|
|
20
|
+
// round trip. The probe is local-only stat checks and conservative: any
|
|
21
|
+
// doubt (size/mtime moved, never synced, no baseline) falls through to a
|
|
22
|
+
// real sync. `--force` always takes the full path.
|
|
23
|
+
if (!opts.force && isLocalTreeClean())
|
|
24
|
+
return;
|
|
18
25
|
// Pass a progress reporter so a large pre-action upload shows the transfer
|
|
19
26
|
// bar instead of a silent pause (the reporter is a no-op on non-TTY / when
|
|
20
27
|
// piped, so JSON and headless output stay clean).
|