@davait/aipictury 1.9.2
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/LICENSE.md +77 -0
- package/README.md +39 -0
- package/ext/about.js +209 -0
- package/ext/account.js +242 -0
- package/ext/accountUi.js +287 -0
- package/ext/agent.js +1929 -0
- package/ext/browser.js +263 -0
- package/ext/cloud.js +385 -0
- package/ext/context.js +241 -0
- package/ext/deps.js +142 -0
- package/ext/dialogs.js +803 -0
- package/ext/diff.js +150 -0
- package/ext/extension.js +2483 -0
- package/ext/feedback.js +55 -0
- package/ext/files.js +565 -0
- package/ext/firestore.js +131 -0
- package/ext/gpuRun.js +77 -0
- package/ext/gpuServer.js +76 -0
- package/ext/hooks.js +153 -0
- package/ext/log.js +29 -0
- package/ext/lsp.js +122 -0
- package/ext/mcp.js +469 -0
- package/ext/mcpAuth.js +128 -0
- package/ext/media/vendor/firebase-config.json +9 -0
- package/ext/media/vendor/signin.js +1 -0
- package/ext/mediagen.js +174 -0
- package/ext/memory.js +117 -0
- package/ext/notebook.js +82 -0
- package/ext/package.json +1234 -0
- package/ext/paths.js +50 -0
- package/ext/permissions.js +229 -0
- package/ext/plans.js +110 -0
- package/ext/plugins.js +243 -0
- package/ext/prefs.js +157 -0
- package/ext/remote.js +170 -0
- package/ext/review.js +126 -0
- package/ext/sandbox.js +79 -0
- package/ext/scheduler.js +87 -0
- package/ext/sessions.js +228 -0
- package/ext/sessionsView.js +449 -0
- package/ext/shell.js +357 -0
- package/ext/storage.js +80 -0
- package/ext/tasklist.js +112 -0
- package/ext/text.js +31 -0
- package/ext/tools.js +781 -0
- package/ext/usage.js +77 -0
- package/ext/voice.js +135 -0
- package/ext/workbench.js +162 -0
- package/ext/workflow.js +51 -0
- package/ext/worktree.js +83 -0
- package/main.js +758 -0
- package/package.json +43 -0
- package/terminal.js +124 -0
- package/vscode-shim.js +231 -0
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@davait/aipictury",
|
|
3
|
+
"version": "1.9.2",
|
|
4
|
+
"description": "The AIPictury agent in a terminal: the VS Code extension's agent, on your own GPU server, signed in to your AIPictury account.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"aipictury": "main.js"
|
|
7
|
+
},
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"prepack": "node pack.mjs",
|
|
13
|
+
"postpack": "node pack.mjs --clean",
|
|
14
|
+
"lint": "../vscode/node_modules/.bin/eslint . --max-warnings=0",
|
|
15
|
+
"test": "node test/cli-check.mjs"
|
|
16
|
+
},
|
|
17
|
+
"author": "Davait, Inc.",
|
|
18
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
19
|
+
"homepage": "https://aipictury.com",
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ai",
|
|
22
|
+
"agent",
|
|
23
|
+
"cli",
|
|
24
|
+
"coding",
|
|
25
|
+
"local",
|
|
26
|
+
"gpu",
|
|
27
|
+
"aipictury"
|
|
28
|
+
],
|
|
29
|
+
"files": [
|
|
30
|
+
"main.js",
|
|
31
|
+
"terminal.js",
|
|
32
|
+
"vscode-shim.js",
|
|
33
|
+
"ext/",
|
|
34
|
+
"LICENSE.md",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"playwright-core": "^1.63.0"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/terminal.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// The terminal's own pieces for the interactive mode: colours, a spinner while the agent works,
|
|
3
|
+
// the answer's Markdown drawn as it streams, and completion for /commands and @files.
|
|
4
|
+
// Copyright © 2026 Davait, Inc. All rights reserved.
|
|
5
|
+
|
|
6
|
+
const tty = !!process.stdout.isTTY
|
|
7
|
+
const paint = (code) => (t) => (tty ? `\x1b[${code}m${t}\x1b[0m` : String(t))
|
|
8
|
+
const dim = paint(2); const bold = paint(1); const italic = paint(3); const red = paint(31); const green = paint(32)
|
|
9
|
+
const yellow = paint(33); const cyan = paint(36); const magenta = paint(35)
|
|
10
|
+
|
|
11
|
+
// What a line looks like on screen, without its colours: for fitting it to the terminal.
|
|
12
|
+
const visible = (s) => String(s).replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '') // eslint-disable-line no-control-regex
|
|
13
|
+
|
|
14
|
+
/** `code`, **bold**, *italic*, [text](url) — inside a line, code spans left as they are. */
|
|
15
|
+
function inline(line) {
|
|
16
|
+
return line.split(/(`[^`]+`)/).map((part) => {
|
|
17
|
+
if (/^`[^`]+`$/.test(part)) return cyan(part.slice(1, -1))
|
|
18
|
+
return part
|
|
19
|
+
.replace(/\*\*([^*]+)\*\*/g, (_, t) => bold(t))
|
|
20
|
+
.replace(/(^|[\s(])\*([^*\s][^*]*)\*(?=[\s).,;:!?]|$)/g, (_, pre, t) => `${pre}${italic(t)}`)
|
|
21
|
+
.replace(/(^|[\s(])_([^_\s][^_]*)_(?=[\s).,;:!?]|$)/g, (_, pre, t) => `${pre}${italic(t)}`)
|
|
22
|
+
.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_, t, url) => `${t} ${dim(`(${url})`)}`)
|
|
23
|
+
}).join('')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** One finished line of Markdown, for the terminal. `state` carries whether it is inside a fence. */
|
|
27
|
+
function markdownLine(line, state) {
|
|
28
|
+
if (/^\s*(```|~~~)/.test(line)) { state.code = !state.code; return dim(line) }
|
|
29
|
+
if (state.code) return `${dim('│')} ${line}`
|
|
30
|
+
let m
|
|
31
|
+
if ((m = /^(#{1,6})\s+(.*)$/.exec(line))) return m[1].length <= 2 ? bold(inline(m[2])) : bold(dim(inline(m[2])))
|
|
32
|
+
if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) return dim('─'.repeat(40))
|
|
33
|
+
if ((m = /^(\s*)>\s?(.*)$/.exec(line))) return `${m[1]}${dim('│')} ${dim(inline(m[2]))}`
|
|
34
|
+
if ((m = /^(\s*)[-*+]\s+\[([ xX])\]\s+(.*)$/.exec(line))) return `${m[1]}${m[2] === ' ' ? '☐' : '☑'} ${inline(m[3])}`
|
|
35
|
+
if ((m = /^(\s*)[-*+]\s+(.*)$/.exec(line))) return `${m[1]}${dim('•')} ${inline(m[2])}`
|
|
36
|
+
if (/^\s*\|.*\|\s*$/.test(line)) return /^\s*\|[\s:|-]+\|\s*$/.test(line) ? dim(line) : inline(line)
|
|
37
|
+
return inline(line)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The answer as it streams: each piece written at once, and each line drawn again as Markdown
|
|
42
|
+
* once it is whole (when it still fits on one terminal row; a longer one stays as it came).
|
|
43
|
+
*/
|
|
44
|
+
class MarkdownStream {
|
|
45
|
+
constructor(out = process.stdout) { this.out = out; this.state = { code: false }; this.line = '' }
|
|
46
|
+
|
|
47
|
+
push(text) {
|
|
48
|
+
const parts = String(text).split('\n')
|
|
49
|
+
parts.forEach((part, i) => {
|
|
50
|
+
if (i > 0) this.finish('\n')
|
|
51
|
+
if (part) { this.out.write(part); this.line += part }
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
finish(end = '') {
|
|
56
|
+
const width = this.out.columns || 80
|
|
57
|
+
const drawn = markdownLine(this.line, this.state)
|
|
58
|
+
if (tty && this.line && visible(this.line).length < width - 1) this.out.write(`\r\x1b[K${drawn}`)
|
|
59
|
+
this.out.write(end)
|
|
60
|
+
this.line = ''
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
end() { if (this.line) this.finish(); this.state = { code: false } }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
67
|
+
|
|
68
|
+
/** "⠹ working · 12s · esc to stop", kept on the line below the output while the agent works. */
|
|
69
|
+
class Spinner {
|
|
70
|
+
constructor({ out = process.stdout, busyTyping = () => false } = {}) {
|
|
71
|
+
this.out = out
|
|
72
|
+
this.busyTyping = busyTyping
|
|
73
|
+
this.on = tty
|
|
74
|
+
this.timer = null
|
|
75
|
+
this.frame = 0
|
|
76
|
+
this.shown = false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
start(label = 'working') {
|
|
80
|
+
if (!this.on) return
|
|
81
|
+
this.label = label
|
|
82
|
+
this.since ||= Date.now()
|
|
83
|
+
if (!this.timer) this.timer = setInterval(() => this.draw(), 100)
|
|
84
|
+
this.draw()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
draw() {
|
|
88
|
+
// Someone typing ahead owns the line: the spinner waits.
|
|
89
|
+
if (this.busyTyping()) { this.clear(); return }
|
|
90
|
+
const secs = Math.round((Date.now() - this.since) / 1000)
|
|
91
|
+
this.out.write(`\r\x1b[K${magenta(FRAMES[this.frame++ % FRAMES.length])} ${dim(`${this.label} · ${secs}s · esc to stop`)}`)
|
|
92
|
+
this.shown = true
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
clear() { if (this.shown) { this.out.write('\r\x1b[K'); this.shown = false } }
|
|
96
|
+
|
|
97
|
+
/** Hidden for output; start() brings it back with the same clock. */
|
|
98
|
+
pause() { if (this.timer) { clearInterval(this.timer); this.timer = null } this.clear() }
|
|
99
|
+
|
|
100
|
+
stop() { this.pause(); this.since = 0 }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Completion for the prompt: /commands at the start of a line, @files anywhere.
|
|
105
|
+
* `commands()` lists the names; `files(query)` resolves to paths (the extension's own fuzzy search).
|
|
106
|
+
*/
|
|
107
|
+
function completer({ commands, files }) {
|
|
108
|
+
return (line, done) => {
|
|
109
|
+
const slash = /^\/([\w:.-]*)$/.exec(line)
|
|
110
|
+
if (slash) {
|
|
111
|
+
const names = commands().map((n) => `/${n}`)
|
|
112
|
+
const hits = names.filter((n) => n.startsWith(line))
|
|
113
|
+
done(null, [hits.length ? hits : names, line])
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
const at = /(?:^|\s)(@[^\s@]*)$/.exec(line)
|
|
117
|
+
if (!at) { done(null, [[], line]); return }
|
|
118
|
+
Promise.resolve(files(at[1].slice(1))).then((found) => {
|
|
119
|
+
done(null, [found.slice(0, 30).map((f) => `@${f}`), at[1]])
|
|
120
|
+
}, () => done(null, [[], line]))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { tty, dim, bold, italic, red, green, yellow, cyan, magenta, visible, inline, markdownLine, MarkdownStream, Spinner, completer }
|
package/vscode-shim.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// The few VS Code APIs the agent's code uses, for the terminal: the CLI runs the extension's own
|
|
3
|
+
// agent (agent.js, tools, rules, hooks, MCP, skills, sandbox) with this module standing in for
|
|
4
|
+
// `vscode`. Files are the disk, the workspace is the folder the CLI was started in, settings come
|
|
5
|
+
// from ~/.aipictury/cli.json (and the extension's defaults), the log is a file. What only VS
|
|
6
|
+
// Code has — language servers, open editors, notebooks, its commands — is absent, and the CLI
|
|
7
|
+
// does not offer the tools that need it.
|
|
8
|
+
const fs = require('fs')
|
|
9
|
+
const os = require('os')
|
|
10
|
+
const path = require('path')
|
|
11
|
+
|
|
12
|
+
const state = {
|
|
13
|
+
root: process.cwd(),
|
|
14
|
+
defaults: {}, // "aipictury.x" → default, from the extension's package.json
|
|
15
|
+
settings: {}, // "aipictury.x" → value, from ~/.aipictury/cli.json and flags
|
|
16
|
+
settingsFile: null,
|
|
17
|
+
logFile: null,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class Uri {
|
|
21
|
+
constructor(scheme, p, extra = {}) {
|
|
22
|
+
this.scheme = scheme
|
|
23
|
+
this.path = p
|
|
24
|
+
this.fsPath = scheme === 'file' ? path.normalize(p) : p
|
|
25
|
+
Object.assign(this, extra)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static file(p) { return new Uri('file', path.resolve(p)) }
|
|
29
|
+
|
|
30
|
+
static parse(text) {
|
|
31
|
+
try {
|
|
32
|
+
const u = new URL(text)
|
|
33
|
+
if (u.protocol === 'file:') return Uri.file(decodeURIComponent(u.pathname))
|
|
34
|
+
return new Uri(u.protocol.slice(0, -1), u.pathname, { authority: u.host, query: u.search.slice(1), fragment: u.hash.slice(1), href: text })
|
|
35
|
+
} catch { return Uri.file(text) }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static joinPath(base, ...parts) { return Uri.file(path.join(base.fsPath, ...parts)) }
|
|
39
|
+
|
|
40
|
+
with(change = {}) { return change.path ? Uri.file(change.path) : this }
|
|
41
|
+
|
|
42
|
+
toString() { return this.scheme === 'file' ? `file://${this.fsPath}` : this.href || `${this.scheme}:${this.path}` }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const FileType = { Unknown: 0, File: 1, Directory: 2, SymbolicLink: 64 }
|
|
46
|
+
const asUri = (u) => (typeof u === 'string' ? Uri.file(u) : u)
|
|
47
|
+
|
|
48
|
+
const fileSystem = {
|
|
49
|
+
async readFile(uri) { return new Uint8Array(await fs.promises.readFile(asUri(uri).fsPath)) },
|
|
50
|
+
async writeFile(uri, data) {
|
|
51
|
+
const p = asUri(uri).fsPath
|
|
52
|
+
await fs.promises.mkdir(path.dirname(p), { recursive: true })
|
|
53
|
+
await fs.promises.writeFile(p, data)
|
|
54
|
+
},
|
|
55
|
+
async stat(uri) {
|
|
56
|
+
const s = await fs.promises.stat(asUri(uri).fsPath)
|
|
57
|
+
return { type: s.isDirectory() ? FileType.Directory : FileType.File, size: s.size, mtime: s.mtimeMs, ctime: s.ctimeMs }
|
|
58
|
+
},
|
|
59
|
+
async readDirectory(uri) {
|
|
60
|
+
const list = await fs.promises.readdir(asUri(uri).fsPath, { withFileTypes: true })
|
|
61
|
+
return list.map((d) => [d.name, d.isDirectory() ? FileType.Directory : d.isSymbolicLink() ? FileType.SymbolicLink : FileType.File])
|
|
62
|
+
},
|
|
63
|
+
async createDirectory(uri) { await fs.promises.mkdir(asUri(uri).fsPath, { recursive: true }) },
|
|
64
|
+
async delete(uri, { recursive = false } = {}) { await fs.promises.rm(asUri(uri).fsPath, { recursive, force: false }) },
|
|
65
|
+
async copy(from, to, { overwrite = false } = {}) {
|
|
66
|
+
await fs.promises.cp(asUri(from).fsPath, asUri(to).fsPath, { recursive: true, force: overwrite, errorOnExist: !overwrite })
|
|
67
|
+
},
|
|
68
|
+
async rename(from, to, { overwrite = false } = {}) {
|
|
69
|
+
if (!overwrite && fs.existsSync(asUri(to).fsPath)) throw new Error(`${asUri(to).fsPath} exists`)
|
|
70
|
+
await fs.promises.rename(asUri(from).fsPath, asUri(to).fsPath)
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class RelativePattern {
|
|
75
|
+
constructor(base, pattern) {
|
|
76
|
+
this.baseUri = typeof base === 'string' ? Uri.file(base) : base.uri || base
|
|
77
|
+
this.base = this.baseUri.fsPath
|
|
78
|
+
this.pattern = pattern
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A glob as a regular expression: ** any depth, * one segment, ? one character, {a,b}. */
|
|
83
|
+
function globRe(glob) {
|
|
84
|
+
let out = ''
|
|
85
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
86
|
+
const c = glob[i]
|
|
87
|
+
if (c === '*' && glob[i + 1] === '*') { i += 1; if (glob[i + 1] === '/') { i += 1; out += '(?:.*/)?' } else out += '.*' } else if (c === '*') out += '[^/]*'
|
|
88
|
+
else if (c === '?') out += '[^/]'
|
|
89
|
+
else if (c === '{') { const end = glob.indexOf('}', i); out += `(?:${glob.slice(i + 1, end).split(',').map((p) => p.replace(/[.+^$()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*')).join('|')})`; i = end } else out += c.replace(/[.+^$()|[\]\\]/g, '\\$&')
|
|
90
|
+
}
|
|
91
|
+
return new RegExp(`^${out}$`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Files under the workspace (or a pattern's base) matching a glob, as VS Code's findFiles. */
|
|
95
|
+
async function findFiles(include, exclude, max = 10_000) {
|
|
96
|
+
const base = include instanceof RelativePattern ? include.base : state.root
|
|
97
|
+
const match = globRe(include instanceof RelativePattern ? include.pattern : include)
|
|
98
|
+
const skip = exclude ? globRe(exclude instanceof RelativePattern ? exclude.pattern : exclude) : null
|
|
99
|
+
const out = []
|
|
100
|
+
const walk = async (dir) => {
|
|
101
|
+
let list
|
|
102
|
+
try { list = await fs.promises.readdir(dir, { withFileTypes: true }) } catch { return }
|
|
103
|
+
for (const d of list) {
|
|
104
|
+
if (out.length >= max) return
|
|
105
|
+
const full = path.join(dir, d.name)
|
|
106
|
+
const rel = path.relative(base, full).split(path.sep).join('/')
|
|
107
|
+
if (d.name === '.git' || (skip && (skip.test(rel) || skip.test(`${rel}/`) || skip.test(`${rel}/x`)))) continue
|
|
108
|
+
if (d.isDirectory()) await walk(full)
|
|
109
|
+
else if (match.test(rel)) out.push(Uri.file(full))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
await walk(base)
|
|
113
|
+
return out
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getConfiguration(section) {
|
|
117
|
+
const key = (k) => (section ? `${section}.${k}` : k)
|
|
118
|
+
return {
|
|
119
|
+
get(k, fallback) {
|
|
120
|
+
const full = key(k)
|
|
121
|
+
if (full in state.settings) return state.settings[full]
|
|
122
|
+
if (full in state.defaults) return state.defaults[full]
|
|
123
|
+
// An object setting asked for by its section ("sandbox" → sandbox.enabled…).
|
|
124
|
+
const prefix = `${full}.`
|
|
125
|
+
const nested = Object.keys({ ...state.defaults, ...state.settings }).filter((n) => n.startsWith(prefix))
|
|
126
|
+
if (nested.length) return Object.fromEntries(nested.map((n) => [n.slice(prefix.length), n in state.settings ? state.settings[n] : state.defaults[n]]))
|
|
127
|
+
return fallback
|
|
128
|
+
},
|
|
129
|
+
has(k) { return key(k) in state.settings || key(k) in state.defaults },
|
|
130
|
+
inspect(k) { return { key: key(k), defaultValue: state.defaults[key(k)], globalValue: state.settings[key(k)] } },
|
|
131
|
+
async update(k, value) {
|
|
132
|
+
const full = key(k)
|
|
133
|
+
if (value === undefined) delete state.settings[full]
|
|
134
|
+
else state.settings[full] = value
|
|
135
|
+
if (state.settingsFile) {
|
|
136
|
+
let saved = {}
|
|
137
|
+
try { saved = JSON.parse(fs.readFileSync(state.settingsFile, 'utf8')) } catch { /* none yet */ }
|
|
138
|
+
if (value === undefined) delete saved[full]; else saved[full] = value
|
|
139
|
+
fs.mkdirSync(path.dirname(state.settingsFile), { recursive: true })
|
|
140
|
+
fs.writeFileSync(state.settingsFile, `${JSON.stringify(saved, null, 2)}\n`)
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const noEvent = () => ({ dispose() {} })
|
|
147
|
+
|
|
148
|
+
class EventEmitter {
|
|
149
|
+
constructor() { this.listeners = new Set(); this.event = (fn) => { this.listeners.add(fn); return { dispose: () => this.listeners.delete(fn) } } }
|
|
150
|
+
fire(value) { for (const fn of this.listeners) fn(value) }
|
|
151
|
+
dispose() { this.listeners.clear() }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function outputChannel() {
|
|
155
|
+
const write = (level) => (text) => {
|
|
156
|
+
if (!state.logFile) return
|
|
157
|
+
try {
|
|
158
|
+
fs.mkdirSync(path.dirname(state.logFile), { recursive: true })
|
|
159
|
+
fs.appendFileSync(state.logFile, `${new Date().toISOString()} [${level}] ${text}\n`)
|
|
160
|
+
} catch { /* a log is never worth failing for */ }
|
|
161
|
+
}
|
|
162
|
+
return { info: write('info'), warn: write('warn'), error: write('error'), appendLine: write('info'), append: write('info'), show() {}, clear() {}, dispose() {} }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Where an installed VS Code keeps its files: its ripgrep is used for search, as in the editor. */
|
|
166
|
+
function appRoot() {
|
|
167
|
+
const candidates = {
|
|
168
|
+
darwin: ['/Applications/Visual Studio Code.app/Contents/Resources/app', path.join(os.homedir(), 'Applications/Visual Studio Code.app/Contents/Resources/app')],
|
|
169
|
+
linux: ['/usr/share/code/resources/app', '/opt/visual-studio-code/resources/app', '/snap/code/current/usr/share/code/resources/app'],
|
|
170
|
+
win32: [path.join(process.env.LOCALAPPDATA || '', 'Programs/Microsoft VS Code/resources/app'), path.join(process.env.PROGRAMFILES || '', 'Microsoft VS Code/resources/app')],
|
|
171
|
+
}[process.platform] || []
|
|
172
|
+
return candidates.find((p) => fs.existsSync(p)) || ''
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const needsEditor = (what) => async () => { throw new Error(`${what} needs VS Code — it is not available in the terminal`) }
|
|
176
|
+
|
|
177
|
+
const workspace = {
|
|
178
|
+
fs: fileSystem,
|
|
179
|
+
get workspaceFolders() { return [{ uri: Uri.file(state.root), name: path.basename(state.root), index: 0 }] },
|
|
180
|
+
getWorkspaceFolder(uri) {
|
|
181
|
+
const p = asUri(uri).fsPath
|
|
182
|
+
return p === state.root || p.startsWith(state.root + path.sep) ? this.workspaceFolders[0] : undefined
|
|
183
|
+
},
|
|
184
|
+
asRelativePath(p) { return path.relative(state.root, typeof p === 'string' ? p : p.fsPath) },
|
|
185
|
+
findFiles,
|
|
186
|
+
textDocuments: [],
|
|
187
|
+
async openTextDocument(uri) {
|
|
188
|
+
const u = asUri(uri)
|
|
189
|
+
const text = await fs.promises.readFile(u.fsPath, 'utf8')
|
|
190
|
+
return { uri: u, isDirty: false, isClosed: false, languageId: path.extname(u.fsPath).slice(1), lineCount: text.split('\n').length, getText: () => text, save: async () => true }
|
|
191
|
+
},
|
|
192
|
+
getConfiguration,
|
|
193
|
+
onDidChangeConfiguration: noEvent,
|
|
194
|
+
onDidChangeTextDocument: noEvent,
|
|
195
|
+
onDidSaveTextDocument: noEvent,
|
|
196
|
+
onDidChangeNotebookDocument: noEvent,
|
|
197
|
+
openNotebookDocument: needsEditor('running a notebook cell'),
|
|
198
|
+
createFileSystemWatcher: () => ({ onDidChange: noEvent, onDidCreate: noEvent, onDidDelete: noEvent, dispose() {} }),
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
version: '1.139.0-cli',
|
|
203
|
+
state,
|
|
204
|
+
Uri,
|
|
205
|
+
FileType,
|
|
206
|
+
RelativePattern,
|
|
207
|
+
EventEmitter,
|
|
208
|
+
Disposable: class { constructor(fn) { this.dispose = fn || (() => {}) } },
|
|
209
|
+
Position: class { constructor(line, character) { this.line = line; this.character = character } },
|
|
210
|
+
Range: class { constructor(a, b, c, d) { this.start = { line: a, character: b }; this.end = { line: c, character: d } } },
|
|
211
|
+
DiagnosticSeverity: { Error: 0, Warning: 1, Information: 2, Hint: 3 },
|
|
212
|
+
ConfigurationTarget: { Global: 1, Workspace: 2, WorkspaceFolder: 3 },
|
|
213
|
+
NotebookCellKind: { Markup: 1, Code: 2 },
|
|
214
|
+
SymbolKind: new Proxy({}, { get: (_, k) => k }),
|
|
215
|
+
workspace,
|
|
216
|
+
languages: { getDiagnostics: () => [], onDidChangeDiagnostics: noEvent },
|
|
217
|
+
window: {
|
|
218
|
+
activeTextEditor: undefined,
|
|
219
|
+
visibleTextEditors: [],
|
|
220
|
+
createOutputChannel: outputChannel,
|
|
221
|
+
showInformationMessage: async () => undefined,
|
|
222
|
+
showWarningMessage: async () => undefined,
|
|
223
|
+
showErrorMessage: async () => undefined,
|
|
224
|
+
showQuickPick: async () => undefined,
|
|
225
|
+
showInputBox: async () => undefined,
|
|
226
|
+
showNotebookDocument: needsEditor('showing a notebook'),
|
|
227
|
+
},
|
|
228
|
+
commands: { executeCommand: needsEditor('that command'), registerCommand: () => ({ dispose() {} }) },
|
|
229
|
+
env: { appRoot: appRoot(), uriScheme: 'vscode', clipboard: { writeText: async () => {}, readText: async () => '' }, openExternal: async () => false },
|
|
230
|
+
extensions: { getExtension: () => undefined },
|
|
231
|
+
}
|