@jackwener/opencli 1.5.3 → 1.5.4
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/.agents/skills/cross-project-adapter-migration/SKILL.md +3 -3
- package/README.md +213 -18
- package/dist/build-manifest.d.ts +2 -3
- package/dist/build-manifest.js +75 -170
- package/dist/build-manifest.test.js +113 -88
- package/dist/cli-manifest.json +1199 -1106
- package/dist/daemon.js +14 -3
- package/dist/external-clis.yaml +16 -0
- package/dist/serialization.js +6 -1
- package/dist/serialization.test.d.ts +1 -0
- package/dist/serialization.test.js +23 -0
- package/extension/dist/background.js +11 -4
- package/extension/manifest.json +2 -2
- package/extension/package-lock.json +2 -2
- package/extension/package.json +1 -1
- package/extension/src/background.ts +19 -5
- package/extension/src/protocol.ts +2 -1
- package/package.json +1 -1
- package/src/build-manifest.test.ts +117 -88
- package/src/build-manifest.ts +81 -180
- package/src/daemon.ts +16 -4
- package/src/external-clis.yaml +16 -0
- package/src/serialization.test.ts +26 -0
- package/src/serialization.ts +6 -1
|
@@ -2,65 +2,8 @@ import { afterEach, describe, expect, it } from 'vitest';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
it('keeps args with nested choices arrays', () => {
|
|
8
|
-
const args = parseTsArgsBlock(`
|
|
9
|
-
{
|
|
10
|
-
name: 'period',
|
|
11
|
-
type: 'string',
|
|
12
|
-
default: 'seven',
|
|
13
|
-
help: 'Stats period: seven or thirty',
|
|
14
|
-
choices: ['seven', 'thirty'],
|
|
15
|
-
},
|
|
16
|
-
`);
|
|
17
|
-
expect(args).toEqual([
|
|
18
|
-
{
|
|
19
|
-
name: 'period',
|
|
20
|
-
type: 'string',
|
|
21
|
-
default: 'seven',
|
|
22
|
-
required: false,
|
|
23
|
-
positional: undefined,
|
|
24
|
-
help: 'Stats period: seven or thirty',
|
|
25
|
-
choices: ['seven', 'thirty'],
|
|
26
|
-
},
|
|
27
|
-
]);
|
|
28
|
-
});
|
|
29
|
-
it('keeps hyphenated arg names from TS adapters', () => {
|
|
30
|
-
const args = parseTsArgsBlock(`
|
|
31
|
-
{
|
|
32
|
-
name: 'tweet-url',
|
|
33
|
-
help: 'Single tweet URL to download',
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
name: 'download-images',
|
|
37
|
-
type: 'boolean',
|
|
38
|
-
default: false,
|
|
39
|
-
help: 'Download images locally',
|
|
40
|
-
},
|
|
41
|
-
`);
|
|
42
|
-
expect(args).toEqual([
|
|
43
|
-
{
|
|
44
|
-
name: 'tweet-url',
|
|
45
|
-
type: 'str',
|
|
46
|
-
default: undefined,
|
|
47
|
-
required: false,
|
|
48
|
-
positional: undefined,
|
|
49
|
-
help: 'Single tweet URL to download',
|
|
50
|
-
choices: undefined,
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: 'download-images',
|
|
54
|
-
type: 'boolean',
|
|
55
|
-
default: false,
|
|
56
|
-
required: false,
|
|
57
|
-
positional: undefined,
|
|
58
|
-
help: 'Download images locally',
|
|
59
|
-
choices: undefined,
|
|
60
|
-
},
|
|
61
|
-
]);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
5
|
+
import { cli, getRegistry, Strategy } from './registry.js';
|
|
6
|
+
import { loadTsManifestEntries, shouldReplaceManifestEntry } from './build-manifest.js';
|
|
64
7
|
describe('manifest helper rules', () => {
|
|
65
8
|
const tempDirs = [];
|
|
66
9
|
afterEach(() => {
|
|
@@ -111,39 +54,121 @@ describe('manifest helper rules', () => {
|
|
|
111
54
|
tempDirs.push(dir);
|
|
112
55
|
const file = path.join(dir, 'utils.ts');
|
|
113
56
|
fs.writeFileSync(file, `export function helper() { return 'noop'; }`);
|
|
114
|
-
expect(
|
|
57
|
+
return expect(loadTsManifestEntries(file, 'demo', async () => ({}))).resolves.toEqual([]);
|
|
115
58
|
});
|
|
116
|
-
it('
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
59
|
+
it('builds TS manifest entries from exported runtime commands', async () => {
|
|
60
|
+
const site = `manifest-hydrate-${Date.now()}`;
|
|
61
|
+
const key = `${site}/dynamic`;
|
|
62
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-'));
|
|
63
|
+
tempDirs.push(dir);
|
|
64
|
+
const file = path.join(dir, `${site}.ts`);
|
|
65
|
+
fs.writeFileSync(file, `export const command = cli({ site: '${site}', name: 'dynamic' });`);
|
|
66
|
+
const entries = await loadTsManifestEntries(file, site, async () => ({
|
|
67
|
+
command: cli({
|
|
68
|
+
site,
|
|
69
|
+
name: 'dynamic',
|
|
70
|
+
description: 'dynamic command',
|
|
71
|
+
strategy: Strategy.PUBLIC,
|
|
72
|
+
browser: false,
|
|
73
|
+
args: [
|
|
74
|
+
{
|
|
75
|
+
name: 'model',
|
|
76
|
+
required: true,
|
|
77
|
+
positional: true,
|
|
78
|
+
help: 'Choose a model',
|
|
79
|
+
choices: ['auto', 'thinking'],
|
|
80
|
+
default: '30',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
domain: 'localhost',
|
|
84
|
+
navigateBefore: 'https://example.com/session',
|
|
85
|
+
deprecated: 'legacy command',
|
|
86
|
+
replacedBy: 'opencli demo new',
|
|
87
|
+
}),
|
|
88
|
+
}));
|
|
89
|
+
expect(entries).toEqual([
|
|
90
|
+
{
|
|
91
|
+
site,
|
|
92
|
+
name: 'dynamic',
|
|
93
|
+
description: 'dynamic command',
|
|
94
|
+
domain: 'localhost',
|
|
95
|
+
strategy: 'public',
|
|
96
|
+
browser: false,
|
|
97
|
+
args: [
|
|
98
|
+
{
|
|
99
|
+
name: 'model',
|
|
100
|
+
type: 'str',
|
|
101
|
+
required: true,
|
|
102
|
+
positional: true,
|
|
103
|
+
help: 'Choose a model',
|
|
104
|
+
choices: ['auto', 'thinking'],
|
|
105
|
+
default: '30',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
type: 'ts',
|
|
109
|
+
modulePath: `${site}/${site}.js`,
|
|
110
|
+
navigateBefore: 'https://example.com/session',
|
|
111
|
+
deprecated: 'legacy command',
|
|
112
|
+
replacedBy: 'opencli demo new',
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
getRegistry().delete(key);
|
|
127
116
|
});
|
|
128
|
-
it('
|
|
117
|
+
it('falls back to registry delta for side-effect-only cli modules', async () => {
|
|
118
|
+
const site = `manifest-side-effect-${Date.now()}`;
|
|
119
|
+
const key = `${site}/legacy`;
|
|
129
120
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-'));
|
|
130
121
|
tempDirs.push(dir);
|
|
131
|
-
const file = path.join(dir,
|
|
132
|
-
fs.writeFileSync(file, `
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
expect(scanTs(file, 'demo')).toMatchObject({
|
|
143
|
-
site: 'demo',
|
|
144
|
-
name: 'legacy',
|
|
145
|
-
deprecated: 'legacy is deprecated',
|
|
146
|
-
replacedBy: 'opencli demo new',
|
|
122
|
+
const file = path.join(dir, `${site}.ts`);
|
|
123
|
+
fs.writeFileSync(file, `cli({ site: '${site}', name: 'legacy' });`);
|
|
124
|
+
const entries = await loadTsManifestEntries(file, site, async () => {
|
|
125
|
+
cli({
|
|
126
|
+
site,
|
|
127
|
+
name: 'legacy',
|
|
128
|
+
description: 'legacy command',
|
|
129
|
+
deprecated: 'legacy is deprecated',
|
|
130
|
+
replacedBy: 'opencli demo new',
|
|
131
|
+
});
|
|
132
|
+
return {};
|
|
147
133
|
});
|
|
134
|
+
expect(entries).toEqual([
|
|
135
|
+
{
|
|
136
|
+
site,
|
|
137
|
+
name: 'legacy',
|
|
138
|
+
description: 'legacy command',
|
|
139
|
+
strategy: 'cookie',
|
|
140
|
+
browser: true,
|
|
141
|
+
args: [],
|
|
142
|
+
type: 'ts',
|
|
143
|
+
modulePath: `${site}/${site}.js`,
|
|
144
|
+
deprecated: 'legacy is deprecated',
|
|
145
|
+
replacedBy: 'opencli demo new',
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
getRegistry().delete(key);
|
|
149
|
+
});
|
|
150
|
+
it('keeps every command a module exports instead of guessing by site', async () => {
|
|
151
|
+
const site = `manifest-multi-${Date.now()}`;
|
|
152
|
+
const screenKey = `${site}/screen`;
|
|
153
|
+
const statusKey = `${site}/status`;
|
|
154
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-'));
|
|
155
|
+
tempDirs.push(dir);
|
|
156
|
+
const file = path.join(dir, `${site}.ts`);
|
|
157
|
+
fs.writeFileSync(file, `export const screen = cli({ site: '${site}', name: 'screen' });`);
|
|
158
|
+
const entries = await loadTsManifestEntries(file, site, async () => ({
|
|
159
|
+
screen: cli({
|
|
160
|
+
site,
|
|
161
|
+
name: 'screen',
|
|
162
|
+
description: 'capture screen',
|
|
163
|
+
}),
|
|
164
|
+
status: cli({
|
|
165
|
+
site,
|
|
166
|
+
name: 'status',
|
|
167
|
+
description: 'show status',
|
|
168
|
+
}),
|
|
169
|
+
}));
|
|
170
|
+
expect(entries.map(entry => entry.name)).toEqual(['screen', 'status']);
|
|
171
|
+
getRegistry().delete(screenKey);
|
|
172
|
+
getRegistry().delete(statusKey);
|
|
148
173
|
});
|
|
149
174
|
});
|