@cmodjs/core 0.1.7 → 0.1.9
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/README.md +2 -2
- package/docs/commands.md +14 -14
- package/docs/dependencies.md +1 -1
- package/docs/hooks.md +69 -13
- package/docs/index.md +7 -5
- package/docs/install-steps.md +19 -19
- package/docs/jobs.md +22 -21
- package/docs/mod.md +44 -12
- package/docs/state.md +8 -8
- package/docs/testing.md +25 -9
- package/docs/ui.md +33 -9
- package/jobs/check.js +2 -2
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +2 -0
- package/jobs/permissions/decide-permission.js +2 -2
- package/jobs/permissions/find-project-scope.d.ts +1 -1
- package/jobs/permissions/find-project-scope.d.ts.map +1 -1
- package/jobs/permissions/find-project-scope.js +9 -9
- package/jobs/permissions/match-target.d.ts.map +1 -1
- package/jobs/permissions/match-target.js +4 -3
- package/mod.d.ts +15 -5
- package/mod.d.ts.map +1 -1
- package/package.json +2 -1
- package/path.d.ts +2 -0
- package/path.d.ts.map +1 -0
- package/path.js +1 -0
- package/records.js +3 -3
- package/register.d.ts.map +1 -1
- package/register.js +8 -2
- package/runtime/claude.d.ts +4 -1
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/hooks.d.ts +10 -2
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +56 -18
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +17 -7
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +38 -8
- package/shell.d.ts +2 -0
- package/shell.d.ts.map +1 -0
- package/shell.js +1 -0
- package/src/jobs/check.ts +2 -2
- package/src/jobs/context.ts +2 -0
- package/src/jobs/permissions/decide-permission.ts +2 -2
- package/src/jobs/permissions/find-project-scope.ts +11 -10
- package/src/jobs/permissions/match-target.ts +4 -3
- package/src/mod.ts +22 -3
- package/src/path.ts +1 -0
- package/src/records.ts +3 -3
- package/src/register.ts +8 -2
- package/src/runtime/claude.ts +11 -1
- package/src/runtime/hooks.ts +62 -23
- package/src/runtime/lifecycle.ts +16 -9
- package/src/runtime/ui.ts +35 -8
- package/src/shell.ts +1 -0
- package/src/testing/fake-claude.ts +14 -5
- package/src/testing/fake-elements.ts +5 -7
- package/src/testing/fake-files.ts +5 -5
- package/src/testing.ts +83 -11
- package/src/ui/define-pane.ts +5 -1
- package/src/utils/call-effects.ts +12 -11
- package/src/utils/parse-shell.ts +28 -36
- package/src/utils/paths.ts +4 -19
- package/src/vendor.ts +1 -0
- package/testing/fake-claude.d.ts +5 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +13 -3
- package/testing/fake-elements.d.ts +5 -8
- package/testing/fake-elements.d.ts.map +1 -1
- package/testing/fake-elements.js +6 -6
- package/testing/fake-files.js +5 -5
- package/testing.d.ts +4 -2
- package/testing.d.ts.map +1 -1
- package/testing.js +65 -9
- package/ui/define-pane.d.ts +5 -1
- package/ui/define-pane.d.ts.map +1 -1
- package/utils/call-effects.d.ts.map +1 -1
- package/utils/call-effects.js +12 -11
- package/utils/parse-shell.d.ts +5 -9
- package/utils/parse-shell.d.ts.map +1 -1
- package/utils/parse-shell.js +5 -10
- package/utils/paths.d.ts +0 -3
- package/utils/paths.d.ts.map +1 -1
- package/utils/paths.js +3 -18
- package/vendor.d.ts +1 -0
- package/vendor.d.ts.map +1 -1
- package/vendor.js +209 -12
package/testing/fake-claude.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { elements } from './fake-elements.js';
|
|
2
2
|
export function fakeClaude(plugin) {
|
|
3
3
|
const calls = [];
|
|
4
|
-
const fakes = { process: {}, fs: {}, http: {}, settings: {}, ui: {}, agent: {}, clock: {}, cmod: {} };
|
|
4
|
+
const fakes = { process: {}, fs: {}, http: {}, settings: {}, ui: {}, session: {}, agent: {}, clock: {}, cmod: {} };
|
|
5
5
|
const shown = { toasts: [], logs: [], debug: [], statuses: [], openPanes: new Set(), commands: [], tools: [] };
|
|
6
6
|
const unplacedPanes = new Set();
|
|
7
|
+
let focusedPane;
|
|
7
8
|
const store = new Map();
|
|
8
9
|
const faked = (call, answer) => {
|
|
9
10
|
return (...args) => {
|
|
@@ -41,6 +42,8 @@ export function fakeClaude(plugin) {
|
|
|
41
42
|
if (answer.isPlaced) {
|
|
42
43
|
shown.openPanes.add(pane.id);
|
|
43
44
|
unplacedPanes.delete(pane.id);
|
|
45
|
+
if (pane.focus === true)
|
|
46
|
+
focusedPane = pane.id;
|
|
44
47
|
}
|
|
45
48
|
else if (!shown.openPanes.has(pane.id)) {
|
|
46
49
|
unplacedPanes.add(pane.id);
|
|
@@ -51,11 +54,14 @@ export function fakeClaude(plugin) {
|
|
|
51
54
|
calls.push({ call: 'ui.close', args: [pane] });
|
|
52
55
|
shown.openPanes.delete(pane.id);
|
|
53
56
|
unplacedPanes.delete(pane.id);
|
|
57
|
+
if (focusedPane === pane.id)
|
|
58
|
+
focusedPane = undefined;
|
|
54
59
|
},
|
|
55
60
|
panes: async () => [
|
|
56
|
-
...[...shown.openPanes].map((id) => ({ id, title: id, isShown: true, isFocused:
|
|
61
|
+
...[...shown.openPanes].map((id) => ({ id, title: id, isShown: true, isFocused: id === focusedPane, isPlaced: true })),
|
|
57
62
|
...[...unplacedPanes].map((id) => ({ id, title: id, isShown: false, isFocused: false, isPlaced: false })),
|
|
58
63
|
],
|
|
64
|
+
scroll: rejected('ui.scroll', () => fakes.ui.scroll ?? (async () => ({}))),
|
|
59
65
|
resolve: () => elements,
|
|
60
66
|
},
|
|
61
67
|
process: {
|
|
@@ -71,7 +77,10 @@ export function fakeClaude(plugin) {
|
|
|
71
77
|
},
|
|
72
78
|
http: { fetch: rejected('http.fetch', () => fakes.http.fetch) },
|
|
73
79
|
settings: { read: rejected('settings.read', () => fakes.settings.read) },
|
|
74
|
-
agent: {
|
|
80
|
+
agent: {
|
|
81
|
+
list: rejected('agent.list', () => fakes.agent.list),
|
|
82
|
+
spawn: rejected('agent.spawn', () => fakes.agent.spawn),
|
|
83
|
+
},
|
|
75
84
|
store: {
|
|
76
85
|
get: async (key) => store.get(key),
|
|
77
86
|
set: async (key, value) => {
|
|
@@ -94,6 +103,7 @@ export function fakeClaude(plugin) {
|
|
|
94
103
|
model: async () => 'test-model',
|
|
95
104
|
usage: async () => ({ context: { window: 200000 }, rateLimits: {}, cost: { usd: 0 } }),
|
|
96
105
|
surfaces: async () => ['terminal'],
|
|
106
|
+
messages: rejected('session.messages', () => fakes.session.messages),
|
|
97
107
|
},
|
|
98
108
|
command: {
|
|
99
109
|
register: async (command) => {
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import type { ElementTable, RenderNode } from 'claude-code';
|
|
2
|
-
type
|
|
3
|
-
readonly type:
|
|
4
|
-
readonly props:
|
|
5
|
-
|
|
6
|
-
readonly label: string;
|
|
7
|
-
readonly onPress: () => unknown;
|
|
8
|
-
};
|
|
2
|
+
type Drawn = {
|
|
3
|
+
readonly type: string;
|
|
4
|
+
readonly props: Record<string, unknown>;
|
|
5
|
+
readonly children?: readonly RenderNode[];
|
|
9
6
|
};
|
|
10
7
|
export declare const elements: ElementTable;
|
|
11
|
-
export declare function
|
|
8
|
+
export declare function findElement(node: RenderNode, type: string, key: string): Drawn['props'] | undefined;
|
|
12
9
|
export declare function rowsOf(node: RenderNode, width: number): string[];
|
|
13
10
|
export declare function textOf(node: RenderNode): string;
|
|
14
11
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-elements.d.ts","sourceRoot":"","sources":["../src/testing/fake-elements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAiB,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"fake-elements.d.ts","sourceRoot":"","sources":["../src/testing/fake-elements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAiB,UAAU,EAAE,MAAM,aAAa,CAAA;AAE1E,KAAK,KAAK,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,UAAU,EAAE,CAAA;CAAE,CAAA;AAiB1H,eAAO,MAAM,QAAQ,EAUL,YAAY,CAAA;AAE5B,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,SAAS,CASnG;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAMhE;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAiB/C"}
|
package/testing/fake-elements.js
CHANGED
|
@@ -23,14 +23,14 @@ export const elements = {
|
|
|
23
23
|
Select: element('Select'),
|
|
24
24
|
Image: element('Image'),
|
|
25
25
|
};
|
|
26
|
-
export function
|
|
26
|
+
export function findElement(node, type, key) {
|
|
27
27
|
if (typeof node === 'string')
|
|
28
28
|
return undefined;
|
|
29
|
-
const
|
|
30
|
-
if (type ===
|
|
31
|
-
return
|
|
32
|
-
for (const child of children) {
|
|
33
|
-
const found =
|
|
29
|
+
const drawn = insideOf(node);
|
|
30
|
+
if (drawn.type === type && (drawn.props['key'] === key || (type === 'Button' && drawn.props['label'] === key)))
|
|
31
|
+
return drawn.props;
|
|
32
|
+
for (const child of drawn.children ?? []) {
|
|
33
|
+
const found = findElement(child, type, key);
|
|
34
34
|
if (found !== undefined)
|
|
35
35
|
return found;
|
|
36
36
|
}
|
package/testing/fake-files.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolve } from '../vendor.js';
|
|
2
2
|
export function fakeFiles(files, links = {}) {
|
|
3
|
-
const contents = new Map(Object.entries(files).map(([path, text]) => [
|
|
3
|
+
const contents = new Map(Object.entries(files).map(([path, text]) => [resolve('/', path), text]));
|
|
4
4
|
const realPathOf = (path) => {
|
|
5
|
-
const absolute =
|
|
5
|
+
const absolute = resolve('/', path);
|
|
6
6
|
for (const [link, target] of Object.entries(links)) {
|
|
7
7
|
if (absolute === link || absolute.startsWith(`${link}/`))
|
|
8
8
|
return realPathOf(`${target}${absolute.slice(link.length)}`);
|
|
@@ -37,7 +37,7 @@ export function fakeFiles(files, links = {}) {
|
|
|
37
37
|
throw new Error(`ENOENT: no such directory, list '${path}'`);
|
|
38
38
|
const names = new Set(below(folder).map((file) => file.split('/')[0]));
|
|
39
39
|
return [...names].map((name) => {
|
|
40
|
-
const entry =
|
|
40
|
+
const entry = resolve(folder, name);
|
|
41
41
|
if (Object.hasOwn(links, entry))
|
|
42
42
|
return { name, kind: 'other', size: 0, mtimeMs: 0, isLink: true };
|
|
43
43
|
return { name, kind: contents.has(entry) ? 'file' : 'dir', size: sizeOf(entry), mtimeMs: 0, isLink: false };
|
|
@@ -47,7 +47,7 @@ export function fakeFiles(files, links = {}) {
|
|
|
47
47
|
stat: async (path, options) => {
|
|
48
48
|
const realPath = realPathOf(path);
|
|
49
49
|
const kind = kindOf(realPath);
|
|
50
|
-
const isLink = Object.hasOwn(links,
|
|
50
|
+
const isLink = Object.hasOwn(links, resolve('/', path));
|
|
51
51
|
if (kind === undefined && isLink)
|
|
52
52
|
return { kind: 'other', size: 0, mtimeMs: 0, isLink };
|
|
53
53
|
if (kind === undefined)
|
package/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Args, ClassicHookInputs, CmodDependencies, EventResult, RenderComponent } from 'claude-code';
|
|
1
|
+
import type { Args, ClassicHookInputs, CmodDependencies, EventResult, RenderComponent, UiInputArgument } from 'claude-code';
|
|
2
2
|
import type { Reply } from './jobs/slash-command.js';
|
|
3
3
|
import type { ModDefinition, ModEvent } from './mod.js';
|
|
4
4
|
import type { RoutedEvent } from './runtime/hooks.js';
|
|
@@ -30,7 +30,9 @@ export type TestedMod<State extends object> = {
|
|
|
30
30
|
}>>(slot: S, props: Omit<SlotProps<S>, 'Default'>, requestId?: string): Promise<string[]>;
|
|
31
31
|
type(line: string): Promise<Reply>;
|
|
32
32
|
callTool(name: string, input: Record<string, unknown>): Promise<EventResult<'tool.call'>>;
|
|
33
|
-
press(paneId: string, key: string): Promise<void>;
|
|
33
|
+
press(paneId: string, key: string, link?: string): Promise<void>;
|
|
34
|
+
input(paneId: string, key: string, text: string, kind?: UiInputArgument['kind']): Promise<void>;
|
|
35
|
+
select(paneId: string, key: string, value: string): Promise<void>;
|
|
34
36
|
moveTo(projectRoot: string, cwd?: string): Promise<void>;
|
|
35
37
|
readonly state: Readonly<State>;
|
|
36
38
|
readonly calls: readonly TestCall[];
|
package/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"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"}
|
package/testing.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { storeFolder } from './records.js';
|
|
2
2
|
import { answerCall, notInstalled } from './runtime/dependencies.js';
|
|
3
|
+
import { toolInputOf } from './runtime/tool-calls.js';
|
|
3
4
|
import { createLifecycle } from './runtime/lifecycle.js';
|
|
4
5
|
import { fakeClaude } from './testing/fake-claude.js';
|
|
5
|
-
import { elements,
|
|
6
|
+
import { elements, findElement, rowsOf } from './testing/fake-elements.js';
|
|
6
7
|
import { fakeFiles } from './testing/fake-files.js';
|
|
7
8
|
const defaultColumns = 80;
|
|
8
9
|
const defaultRows = 24;
|
|
@@ -24,6 +25,13 @@ export function testMod(definition, options = {}) {
|
|
|
24
25
|
fake.claude.session.root = async () => projectRoot;
|
|
25
26
|
fake.claude.session.cwd = async () => cwd;
|
|
26
27
|
fake.fakes.cmod.call = fakeDependencies(options.dependencies ?? {});
|
|
28
|
+
const firedAgents = new Map();
|
|
29
|
+
const listAgents = fake.claude.agent.list;
|
|
30
|
+
fake.claude.agent.list = async () => {
|
|
31
|
+
if (fake.fakes.agent.list !== undefined || firedAgents.size === 0)
|
|
32
|
+
return listAgents();
|
|
33
|
+
return [...firedAgents].map(([id, type]) => ({ id, type, description: '', status: 'running' }));
|
|
34
|
+
};
|
|
27
35
|
Object.assign(fake.fakes.fs, fakeFiles(options.files ?? {}));
|
|
28
36
|
const starting = options.state;
|
|
29
37
|
const declared = Object.entries(definition.state ?? {});
|
|
@@ -55,10 +63,18 @@ export function testMod(definition, options = {}) {
|
|
|
55
63
|
}
|
|
56
64
|
const opened = fake.calls.findLast((call) => call.call === 'ui.open' && call.args[0].id === paneId)?.args[0];
|
|
57
65
|
const columns = opened?.columns ?? defaultColumns;
|
|
58
|
-
const
|
|
66
|
+
const isFocused = (await fake.claude.ui.panes()).some((open) => open.id === paneId && open.isFocused);
|
|
67
|
+
const props = { title: opened?.title ?? paneId, isFocused, bodyColumns: columns, placement: 'dock', scroll: { offset: 0, bodyRows: defaultRows }, view: {} };
|
|
59
68
|
const drawing = await route('ui.render', { surface: 'terminal', component: 'Pane', requestId: paneId, props }, elements.Box({}));
|
|
60
69
|
return { drawing, columns };
|
|
61
70
|
};
|
|
71
|
+
const drawnElement = async (paneId, type, key) => {
|
|
72
|
+
const props = findElement((await drawPane(paneId)).drawing, type, key);
|
|
73
|
+
if (props === undefined)
|
|
74
|
+
throw new Error(`The pane "${paneId}" of ${name} draws no ${type} with the ${type === 'Button' ? 'key or label' : 'key'} "${key}".`);
|
|
75
|
+
return props;
|
|
76
|
+
};
|
|
77
|
+
const eventIn = (paneId, key) => ({ plugin: name, element: key, component: 'Pane', requestId: paneId, surface: 'terminal' });
|
|
62
78
|
const drawSlot = async (component, props, requestId = `${component}_${(slotDraws += 1)}`) => {
|
|
63
79
|
await start();
|
|
64
80
|
const input = { surface: 'terminal', component, requestId, props };
|
|
@@ -78,9 +94,29 @@ export function testMod(definition, options = {}) {
|
|
|
78
94
|
const filled = { session_id: sessionId, transcript_path: '/test/transcript.jsonl', cwd, hook_event_name: event, ...toolUse, ...input };
|
|
79
95
|
if (event !== 'PreToolUse')
|
|
80
96
|
return route(`classic.${event}`, filled, below);
|
|
81
|
-
|
|
82
|
-
return route('classic.PreToolUse', { ...tool_input, tool: tool_name, tool_use_id }, below);
|
|
97
|
+
return firePreToolUse(filled, below);
|
|
83
98
|
});
|
|
99
|
+
const firePreToolUse = async ({ tool_name, tool_input, tool_use_id, agent_id, agent_type }, below) => {
|
|
100
|
+
await start();
|
|
101
|
+
if (agent_id !== undefined && agent_type !== undefined)
|
|
102
|
+
firedAgents.set(agent_id, agent_type);
|
|
103
|
+
const envelope = { ...tool_input, tool: tool_name, tool_use_id, ...(agent_id === undefined ? {} : { agentId: agent_id }) };
|
|
104
|
+
const asks = below?.deny === undefined ? { decision: 'ask' } : { decision: 'deny', reason: below.deny };
|
|
105
|
+
let reached = envelope;
|
|
106
|
+
let check = asks;
|
|
107
|
+
const answer = await lifecycle.route('tool.call', envelope, async (e) => {
|
|
108
|
+
reached = e;
|
|
109
|
+
check = await lifecycle.route('tool.check', { tool: e.tool, input: toolInputOf(e), tool_use_id: e.tool_use_id, ...(e.agentId === undefined ? {} : { agentId: e.agentId }) }, async () => asks);
|
|
110
|
+
return (check.decision === 'deny' ? { deny: check.reason ?? '' } : { result: '' });
|
|
111
|
+
});
|
|
112
|
+
const updatedInput = toolInputOf(reached);
|
|
113
|
+
const decided = check === asks ? {} : check.decision === 'allow' ? { allow: true } : check.decision === 'ask' ? { ask: check.reason ?? '' } : {};
|
|
114
|
+
return {
|
|
115
|
+
...(answer.deny === undefined ? decided : { deny: answer.deny }),
|
|
116
|
+
...(JSON.stringify(updatedInput) === JSON.stringify(tool_input) ? {} : { updatedInput }),
|
|
117
|
+
...(answer.context === undefined || answer.context.length === 0 ? {} : { additionalContext: [...answer.context] }),
|
|
118
|
+
};
|
|
119
|
+
};
|
|
84
120
|
return {
|
|
85
121
|
start,
|
|
86
122
|
fire,
|
|
@@ -114,11 +150,31 @@ export function testMod(definition, options = {}) {
|
|
|
114
150
|
throw new Error(`${name} has no tool ${toolName}. Add it in setup with mod.use(tool({ name: '${toolName}', … })).`);
|
|
115
151
|
return answer;
|
|
116
152
|
},
|
|
117
|
-
async press(paneId, key) {
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
throw new Error(`The pane "${paneId}" of ${name}
|
|
121
|
-
|
|
153
|
+
async press(paneId, key, link) {
|
|
154
|
+
const props = await drawnElement(paneId, link === undefined ? 'Button' : 'Markdown', key);
|
|
155
|
+
if (link !== undefined && props['onLinkPress'] === undefined)
|
|
156
|
+
throw new Error(`The Markdown "${key}" in the pane "${paneId}" of ${name} has no onLinkPress, so Claude Code opens its links itself.`);
|
|
157
|
+
const pressed = { ...eventIn(paneId, String(props['key'])), ...(link === undefined ? {} : { link: { href: link } }) };
|
|
158
|
+
await lifecycle.route('ui.press', pressed, async (e) => {
|
|
159
|
+
if (e.link === undefined)
|
|
160
|
+
await props['onPress'](e);
|
|
161
|
+
else
|
|
162
|
+
await props['onLinkPress'](e.link, e);
|
|
163
|
+
return { element: e.element };
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
async input(paneId, key, text, kind = 'submit') {
|
|
167
|
+
const props = await drawnElement(paneId, 'Input', key);
|
|
168
|
+
const typed = { ...eventIn(paneId, key), kind, value: text };
|
|
169
|
+
await props[kind === 'submit' ? 'onSubmit' : 'onInput']?.(text, typed);
|
|
170
|
+
},
|
|
171
|
+
async select(paneId, key, value) {
|
|
172
|
+
const props = await drawnElement(paneId, 'Select', key);
|
|
173
|
+
const options = props['options'].map((option) => option.value);
|
|
174
|
+
if (!options.includes(value))
|
|
175
|
+
throw new Error(`The Select "${key}" in the pane "${paneId}" of ${name} has no option "${value}". Its options are ${options.map((option) => `"${option}"`).join(', ')}.`);
|
|
176
|
+
const picked = { ...eventIn(paneId, key), value };
|
|
177
|
+
await props['onSelect'](value, picked);
|
|
122
178
|
},
|
|
123
179
|
async moveTo(nextRoot, nextCwd = nextRoot) {
|
|
124
180
|
await start();
|
package/ui/define-pane.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import type { Frozen, RenderElement, RenderPropsOf } from 'claude-code';
|
|
1
|
+
import type { Frozen, PaneCloseInput, RenderElement, RenderPropsOf, UiScrollInput } from 'claude-code';
|
|
2
2
|
import type { Mod } from '../mod.js';
|
|
3
3
|
export type Pane<State extends object = Record<never, never>> = {
|
|
4
4
|
readonly id: string;
|
|
5
5
|
readonly title: string;
|
|
6
6
|
readonly columns?: number | ((state: State) => number | undefined);
|
|
7
7
|
readonly rows?: number | ((state: State) => number | undefined);
|
|
8
|
+
readonly closeOnEscape?: true;
|
|
9
|
+
readonly holdToasts?: true;
|
|
8
10
|
render(mod: Mod<State>, props: Frozen<RenderPropsOf['Pane']>): RenderElement;
|
|
11
|
+
onScroll?(mod: Mod<State>, e: Frozen<UiScrollInput>): void | Promise<void>;
|
|
12
|
+
onClose?(mod: Mod<State>, e: Frozen<PaneCloseInput>): void | Promise<void>;
|
|
9
13
|
};
|
|
10
14
|
export declare function definePane<State extends object = Record<never, never>>(pane: Pane<State>): Pane<State>;
|
|
11
15
|
//# sourceMappingURL=define-pane.d.ts.map
|
package/ui/define-pane.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-pane.d.ts","sourceRoot":"","sources":["../src/ui/define-pane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"define-pane.d.ts","sourceRoot":"","sources":["../src/ui/define-pane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACtG,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAEpC,MAAM,MAAM,IAAI,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI;IAC9D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,SAAS,CAAC,CAAA;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,SAAS,CAAC,CAAA;IAC/D,QAAQ,CAAC,aAAa,CAAC,EAAE,IAAI,CAAA;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAA;IAC1B,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,aAAa,CAAA;IAC5E,QAAQ,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,aAAa,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1E,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3E,CAAA;AAMD,wBAAgB,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAOtG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-effects.d.ts","sourceRoot":"","sources":["../src/utils/call-effects.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"call-effects.d.ts","sourceRoot":"","sources":["../src/utils/call-effects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAGxD,MAAM,MAAM,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5F,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAC7E,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG;IAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,CAAA;AAClG,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAC9F,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAClD,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEpE,MAAM,MAAM,QAAQ,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AACrE,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,SAAS,CAAA;CAAE,CAAA;AAC1F,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAA;IACrF,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAC7B,CAAA;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,GAAG,WAAW,CAsC/G;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,QAAQ,CAE1E"}
|
package/utils/call-effects.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { expandHome
|
|
1
|
+
import { parseShell } from './parse-shell.js';
|
|
2
|
+
import { expandHome } from './paths.js';
|
|
3
|
+
import { resolve } from '../vendor.js';
|
|
3
4
|
export function callEffects(use, workspace) {
|
|
4
5
|
const { fs } = workspace;
|
|
5
|
-
const
|
|
6
|
-
const access = (path) => ({ path:
|
|
6
|
+
const absolute = (path, folder = workspace.cwd) => resolve(folder, expandHome(path, workspace.home));
|
|
7
|
+
const access = (path) => ({ path: absolute(path), contents: undefined });
|
|
7
8
|
const effects = { shell: undefined, reads: [], writes: [], urls: [], subagent: undefined };
|
|
8
9
|
if (use.tool === 'Bash') {
|
|
9
10
|
const line = textField(use, 'command');
|
|
10
|
-
const parsed =
|
|
11
|
-
const commands = parsed.commands.map(({ argv, folder }) => ({ argv, folder:
|
|
11
|
+
const parsed = parseShell(line);
|
|
12
|
+
const commands = parsed.commands.map(({ argv, folder }) => ({ argv, folder: absolute(folder) }));
|
|
12
13
|
effects.shell = { line, commands, isFullyParsed: parsed.isFullyParsed };
|
|
13
14
|
effects.reads = parsed.reads.map(access);
|
|
14
15
|
effects.writes = parsed.writes.map(access);
|
|
@@ -18,16 +19,16 @@ export function callEffects(use, workspace) {
|
|
|
18
19
|
effects.shell = { line: textField(use, 'command'), commands: [], isFullyParsed: false };
|
|
19
20
|
}
|
|
20
21
|
else if (use.tool === 'Edit') {
|
|
21
|
-
const path =
|
|
22
|
+
const path = absolute(textField(use, 'file_path'));
|
|
22
23
|
effects.writes = [{ path, contents: once(() => editedContents(use, path, fs)) }];
|
|
23
24
|
}
|
|
24
25
|
else if (use.tool === 'Write') {
|
|
25
|
-
const path =
|
|
26
|
+
const path = absolute(textField(use, 'file_path'));
|
|
26
27
|
const content = textField(use, 'content');
|
|
27
28
|
effects.writes = [{ path, contents: once(async () => contentsOf(content, await previousContentOf(path, fs))) }];
|
|
28
29
|
}
|
|
29
30
|
else if (use.tool === 'NotebookEdit') {
|
|
30
|
-
const path =
|
|
31
|
+
const path = absolute(textField(use, 'notebook_path'));
|
|
31
32
|
effects.writes = [{ path, contents: once(async () => contentsOf(undefined, await previousContentOf(path, fs))) }];
|
|
32
33
|
}
|
|
33
34
|
else if (use.tool === 'Read') {
|
|
@@ -37,8 +38,8 @@ export function callEffects(use, workspace) {
|
|
|
37
38
|
effects.reads = [access(optionalTextField(use, 'path') ?? '')];
|
|
38
39
|
}
|
|
39
40
|
else if (use.tool === 'Glob') {
|
|
40
|
-
const folder =
|
|
41
|
-
effects.reads = [{ path:
|
|
41
|
+
const folder = absolute(optionalTextField(use, 'path') ?? '');
|
|
42
|
+
effects.reads = [{ path: absolute(textField(use, 'pattern'), folder), contents: undefined }];
|
|
42
43
|
}
|
|
43
44
|
else if (use.tool === 'WebFetch') {
|
|
44
45
|
effects.urls = [textField(use, 'url')];
|
package/utils/parse-shell.d.ts
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
export type ParsedShell = {
|
|
2
|
-
commands: [string, ...string[]][];
|
|
3
|
-
writes: string[];
|
|
4
|
-
reads: string[];
|
|
5
|
-
fetches: string[];
|
|
6
|
-
isFullyParsed: boolean;
|
|
7
|
-
};
|
|
8
1
|
export type ShellCommand = {
|
|
9
2
|
argv: [string, ...string[]];
|
|
10
3
|
folder: string;
|
|
11
4
|
};
|
|
12
|
-
export type
|
|
5
|
+
export type ParsedShell = {
|
|
13
6
|
commands: ShellCommand[];
|
|
7
|
+
writes: string[];
|
|
8
|
+
reads: string[];
|
|
9
|
+
fetches: string[];
|
|
10
|
+
isFullyParsed: boolean;
|
|
14
11
|
};
|
|
15
12
|
export declare const dynamicPattern: RegExp;
|
|
16
13
|
export declare function parseShell(line: string): ParsedShell;
|
|
17
|
-
export declare function parseShellCommands(line: string): ParsedShellCommands;
|
|
18
14
|
//# sourceMappingURL=parse-shell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-shell.d.ts","sourceRoot":"","sources":["../src/utils/parse-shell.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"parse-shell.d.ts","sourceRoot":"","sources":["../src/utils/parse-shell.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1E,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA;AA2BD,eAAO,MAAM,cAAc,QAAS,CAAA;AAiKpC,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAIpD"}
|
package/utils/parse-shell.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { parse } from '../vendor.js';
|
|
1
|
+
import { basename, parse } from '../vendor.js';
|
|
3
2
|
const markerPattern = /\u0001(\d+)\u0001/g;
|
|
4
3
|
const descriptorPattern = /[0-9]+(?=[<>])/y;
|
|
5
4
|
const assignmentPattern = /^[A-Za-z_][A-Za-z0-9_]*(\[[^\]]*\])?\+?=/;
|
|
@@ -163,10 +162,6 @@ const inlineCodeFlags = new Map([
|
|
|
163
162
|
['osascript', ['-e']],
|
|
164
163
|
]);
|
|
165
164
|
export function parseShell(line) {
|
|
166
|
-
const parsed = parseShellCommands(line);
|
|
167
|
-
return { ...parsed, commands: parsed.commands.map((command) => command.argv) };
|
|
168
|
-
}
|
|
169
|
-
export function parseShellCommands(line) {
|
|
170
165
|
const result = { commands: [], writes: [], reads: [], fetches: [], isFullyParsed: true };
|
|
171
166
|
parseLine(line, '', result);
|
|
172
167
|
return result;
|
|
@@ -533,7 +528,7 @@ function wordOf(token, markers, folder, result) {
|
|
|
533
528
|
return {
|
|
534
529
|
text: raw.replace(markerPattern, (_, index) => markers[Number(index)].raw),
|
|
535
530
|
isGlob: typeof token !== 'string',
|
|
536
|
-
isDynamic: /[$\u0001]/.test(
|
|
531
|
+
isDynamic: /[$\u0001]/.test(basename(raw)),
|
|
537
532
|
};
|
|
538
533
|
}
|
|
539
534
|
function finish(command, operator, folder, result) {
|
|
@@ -573,7 +568,7 @@ function addRedirect({ operator, target }, folder, result) {
|
|
|
573
568
|
function addCommand(first, args, folder, stdin, result) {
|
|
574
569
|
if (first.isDynamic || first.isGlob)
|
|
575
570
|
result.isFullyParsed = false;
|
|
576
|
-
const program =
|
|
571
|
+
const program = basename(first.text);
|
|
577
572
|
if (program === 'git')
|
|
578
573
|
addGit(first, args, folder, result);
|
|
579
574
|
else if (program === 'find')
|
|
@@ -734,7 +729,7 @@ function addCopy(program, args, folder, result) {
|
|
|
734
729
|
const target = options.findLast((option) => program.targetOptions?.includes(option.name) === true)?.value;
|
|
735
730
|
const only = operands[0];
|
|
736
731
|
if (program.linksHere === true && target === undefined && operands.length === 1 && only !== undefined) {
|
|
737
|
-
addPath(result, 'writes', { ...only, text:
|
|
732
|
+
addPath(result, 'writes', { ...only, text: basename(only.text) }, folder);
|
|
738
733
|
return;
|
|
739
734
|
}
|
|
740
735
|
const destination = target ?? operands.at(-1);
|
|
@@ -746,7 +741,7 @@ function addCopy(program, args, folder, result) {
|
|
|
746
741
|
if (program.sources !== undefined)
|
|
747
742
|
addPath(result, program.sources, source, folder);
|
|
748
743
|
if (isLocal(destination) && !source.isGlob && !destination.isGlob)
|
|
749
|
-
addPathText(result, 'writes', `${destination.text.replace(/\/+$/, '')}/${
|
|
744
|
+
addPathText(result, 'writes', `${destination.text.replace(/\/+$/, '')}/${basename(source.text)}`, folder);
|
|
750
745
|
}
|
|
751
746
|
if (isLocal(destination))
|
|
752
747
|
addPath(result, 'writes', destination, folder);
|
package/utils/paths.d.ts
CHANGED
|
@@ -7,9 +7,6 @@ export type FileSystem = {
|
|
|
7
7
|
exists: (path: string) => Promise<boolean>;
|
|
8
8
|
};
|
|
9
9
|
export declare function expandHome(path: string, home: string): string;
|
|
10
|
-
export declare function resolvePath(path: string, folder: string): string;
|
|
11
|
-
export declare function parentOf(path: string): string;
|
|
12
|
-
export declare function nameOf(path: string): string;
|
|
13
10
|
export declare function relativePath(base: string, path: string): string | undefined;
|
|
14
11
|
export declare function realPathOf(path: string, fs: FileSystem): Promise<string | undefined>;
|
|
15
12
|
//# sourceMappingURL=paths.d.ts.map
|
package/utils/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/utils/paths.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/utils/paths.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACvC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtF,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC3C,CAAA;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI3E;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAK1F"}
|
package/utils/paths.js
CHANGED
|
@@ -1,23 +1,8 @@
|
|
|
1
|
+
import { basename, dirname, resolve } from '../vendor.js';
|
|
1
2
|
export function expandHome(path, home) {
|
|
2
3
|
const prefix = /^(~|\$HOME)(?=\/|$)/.exec(path)?.[0];
|
|
3
4
|
return prefix === undefined ? path : `${home}${path.slice(prefix.length)}`;
|
|
4
5
|
}
|
|
5
|
-
export function resolvePath(path, folder) {
|
|
6
|
-
const parts = [];
|
|
7
|
-
for (const part of (path.startsWith('/') ? path : `${folder}/${path}`).split('/')) {
|
|
8
|
-
if (part === '..')
|
|
9
|
-
parts.pop();
|
|
10
|
-
else if (part !== '' && part !== '.')
|
|
11
|
-
parts.push(part);
|
|
12
|
-
}
|
|
13
|
-
return `/${parts.join('/')}`;
|
|
14
|
-
}
|
|
15
|
-
export function parentOf(path) {
|
|
16
|
-
return resolvePath('..', path);
|
|
17
|
-
}
|
|
18
|
-
export function nameOf(path) {
|
|
19
|
-
return path.slice(path.lastIndexOf('/') + 1);
|
|
20
|
-
}
|
|
21
6
|
export function relativePath(base, path) {
|
|
22
7
|
if (path === base)
|
|
23
8
|
return '';
|
|
@@ -28,6 +13,6 @@ export async function realPathOf(path, fs) {
|
|
|
28
13
|
const stat = await fs.stat(path).catch(() => undefined);
|
|
29
14
|
if (stat !== undefined || path === '/')
|
|
30
15
|
return stat?.realPath;
|
|
31
|
-
const folder = await realPathOf(
|
|
32
|
-
return folder === undefined ? undefined :
|
|
16
|
+
const folder = await realPathOf(dirname(path), fs);
|
|
17
|
+
return folder === undefined ? undefined : resolve(folder, basename(path));
|
|
33
18
|
}
|
package/vendor.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Validator } from '@cfworker/json-schema';
|
|
2
|
+
export { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve } from 'pathe';
|
|
2
3
|
export { default as picomatch } from 'picomatch/posix';
|
|
3
4
|
export { parse, quote, type GlobPattern, type ParseEntry } from 'shell-quote';
|
|
4
5
|
//# sourceMappingURL=vendor.d.ts.map
|
package/vendor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vendor.d.ts","sourceRoot":"","sources":["src/vendor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"vendor.d.ts","sourceRoot":"","sources":["src/vendor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAClG,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA"}
|