@taliesinb/mash 0.0.0-stage → 0.1.0
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 +21 -0
- package/README.md +275 -2
- package/bin/mash.ts +253 -0
- package/dist/bin/mash.d.ts +39 -0
- package/dist/bin/mash.d.ts.map +1 -0
- package/dist/bin/mash.js +281 -0
- package/dist/bin/mash.js.map +1 -0
- package/dist/src/analysis/analyze.d.ts +97 -0
- package/dist/src/analysis/analyze.d.ts.map +1 -0
- package/dist/src/analysis/analyze.js +1406 -0
- package/dist/src/analysis/analyze.js.map +1 -0
- package/dist/src/analysis/fs.d.ts +39 -0
- package/dist/src/analysis/fs.d.ts.map +1 -0
- package/dist/src/analysis/fs.js +117 -0
- package/dist/src/analysis/fs.js.map +1 -0
- package/dist/src/analysis/outputs.d.ts +16 -0
- package/dist/src/analysis/outputs.d.ts.map +1 -0
- package/dist/src/analysis/outputs.js +277 -0
- package/dist/src/analysis/outputs.js.map +1 -0
- package/dist/src/analysis/value.d.ts +76 -0
- package/dist/src/analysis/value.d.ts.map +1 -0
- package/dist/src/analysis/value.js +248 -0
- package/dist/src/analysis/value.js.map +1 -0
- package/dist/src/behaviors/defs/core.d.ts +15 -0
- package/dist/src/behaviors/defs/core.d.ts.map +1 -0
- package/dist/src/behaviors/defs/core.js +1084 -0
- package/dist/src/behaviors/defs/core.js.map +1 -0
- package/dist/src/behaviors/defs/devtools.d.ts +9 -0
- package/dist/src/behaviors/defs/devtools.d.ts.map +1 -0
- package/dist/src/behaviors/defs/devtools.js +432 -0
- package/dist/src/behaviors/defs/devtools.js.map +1 -0
- package/dist/src/behaviors/defs/macos.d.ts +12 -0
- package/dist/src/behaviors/defs/macos.d.ts.map +1 -0
- package/dist/src/behaviors/defs/macos.js +454 -0
- package/dist/src/behaviors/defs/macos.js.map +1 -0
- package/dist/src/behaviors/defs/tools.d.ts +9 -0
- package/dist/src/behaviors/defs/tools.d.ts.map +1 -0
- package/dist/src/behaviors/defs/tools.js +639 -0
- package/dist/src/behaviors/defs/tools.js.map +1 -0
- package/dist/src/behaviors/dsl.d.ts +95 -0
- package/dist/src/behaviors/dsl.d.ts.map +1 -0
- package/dist/src/behaviors/dsl.js +267 -0
- package/dist/src/behaviors/dsl.js.map +1 -0
- package/dist/src/behaviors/index.d.ts +12 -0
- package/dist/src/behaviors/index.d.ts.map +1 -0
- package/dist/src/behaviors/index.js +18 -0
- package/dist/src/behaviors/index.js.map +1 -0
- package/dist/src/behaviors/registry.d.ts +45 -0
- package/dist/src/behaviors/registry.d.ts.map +1 -0
- package/dist/src/behaviors/registry.js +266 -0
- package/dist/src/behaviors/registry.js.map +1 -0
- package/dist/src/behaviors/roles.d.ts +29 -0
- package/dist/src/behaviors/roles.d.ts.map +1 -0
- package/dist/src/behaviors/roles.js +198 -0
- package/dist/src/behaviors/roles.js.map +1 -0
- package/dist/src/behaviors/types.d.ts +120 -0
- package/dist/src/behaviors/types.d.ts.map +1 -0
- package/dist/src/behaviors/types.js +12 -0
- package/dist/src/behaviors/types.js.map +1 -0
- package/dist/src/index.d.ts +27 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +24 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/integrations/deepsea.d.ts +121 -0
- package/dist/src/integrations/deepsea.d.ts.map +1 -0
- package/dist/src/integrations/deepsea.js +199 -0
- package/dist/src/integrations/deepsea.js.map +1 -0
- package/dist/src/machine.d.ts +139 -0
- package/dist/src/machine.d.ts.map +1 -0
- package/dist/src/machine.js +400 -0
- package/dist/src/machine.js.map +1 -0
- package/dist/src/parser/ast.d.ts +369 -0
- package/dist/src/parser/ast.d.ts.map +1 -0
- package/dist/src/parser/ast.js +17 -0
- package/dist/src/parser/ast.js.map +1 -0
- package/dist/src/parser/parser.d.ts +48 -0
- package/dist/src/parser/parser.d.ts.map +1 -0
- package/dist/src/parser/parser.js +1950 -0
- package/dist/src/parser/parser.js.map +1 -0
- package/dist/src/policy/effects.d.ts +86 -0
- package/dist/src/policy/effects.d.ts.map +1 -0
- package/dist/src/policy/effects.js +53 -0
- package/dist/src/policy/effects.js.map +1 -0
- package/dist/src/policy/policy.d.ts +130 -0
- package/dist/src/policy/policy.d.ts.map +1 -0
- package/dist/src/policy/policy.js +532 -0
- package/dist/src/policy/policy.js.map +1 -0
- package/dist/src/printer/doc.d.ts +151 -0
- package/dist/src/printer/doc.d.ts.map +1 -0
- package/dist/src/printer/doc.js +409 -0
- package/dist/src/printer/doc.js.map +1 -0
- package/dist/src/printer/highlight.d.ts +53 -0
- package/dist/src/printer/highlight.d.ts.map +1 -0
- package/dist/src/printer/highlight.js +339 -0
- package/dist/src/printer/highlight.js.map +1 -0
- package/dist/src/printer/html.d.ts +72 -0
- package/dist/src/printer/html.d.ts.map +1 -0
- package/dist/src/printer/html.js +423 -0
- package/dist/src/printer/html.js.map +1 -0
- package/dist/src/printer/print.d.ts +63 -0
- package/dist/src/printer/print.d.ts.map +1 -0
- package/dist/src/printer/print.js +484 -0
- package/dist/src/printer/print.js.map +1 -0
- package/dist/src/printer/summary.d.ts +50 -0
- package/dist/src/printer/summary.d.ts.map +1 -0
- package/dist/src/printer/summary.js +621 -0
- package/dist/src/printer/summary.js.map +1 -0
- package/dist/src/trace/events.d.ts +183 -0
- package/dist/src/trace/events.d.ts.map +1 -0
- package/dist/src/trace/events.js +9 -0
- package/dist/src/trace/events.js.map +1 -0
- package/dist/src/trace/progress.d.ts +58 -0
- package/dist/src/trace/progress.d.ts.map +1 -0
- package/dist/src/trace/progress.js +83 -0
- package/dist/src/trace/progress.js.map +1 -0
- package/dist/src/vm/arith.d.ts +29 -0
- package/dist/src/vm/arith.d.ts.map +1 -0
- package/dist/src/vm/arith.js +258 -0
- package/dist/src/vm/arith.js.map +1 -0
- package/dist/src/vm/builtins.d.ts +31 -0
- package/dist/src/vm/builtins.d.ts.map +1 -0
- package/dist/src/vm/builtins.js +1773 -0
- package/dist/src/vm/builtins.js.map +1 -0
- package/dist/src/vm/emulate.d.ts +75 -0
- package/dist/src/vm/emulate.d.ts.map +1 -0
- package/dist/src/vm/emulate.js +380 -0
- package/dist/src/vm/emulate.js.map +1 -0
- package/dist/src/vm/expand.d.ts +68 -0
- package/dist/src/vm/expand.d.ts.map +1 -0
- package/dist/src/vm/expand.js +744 -0
- package/dist/src/vm/expand.js.map +1 -0
- package/dist/src/vm/external.d.ts +34 -0
- package/dist/src/vm/external.d.ts.map +1 -0
- package/dist/src/vm/external.js +228 -0
- package/dist/src/vm/external.js.map +1 -0
- package/dist/src/vm/fifo.d.ts +66 -0
- package/dist/src/vm/fifo.d.ts.map +1 -0
- package/dist/src/vm/fifo.js +232 -0
- package/dist/src/vm/fifo.js.map +1 -0
- package/dist/src/vm/interp.d.ts +209 -0
- package/dist/src/vm/interp.d.ts.map +1 -0
- package/dist/src/vm/interp.js +1641 -0
- package/dist/src/vm/interp.js.map +1 -0
- package/dist/src/vm/io.d.ts +180 -0
- package/dist/src/vm/io.d.ts.map +1 -0
- package/dist/src/vm/io.js +502 -0
- package/dist/src/vm/io.js.map +1 -0
- package/dist/src/vm/pattern.d.ts +46 -0
- package/dist/src/vm/pattern.d.ts.map +1 -0
- package/dist/src/vm/pattern.js +498 -0
- package/dist/src/vm/pattern.js.map +1 -0
- package/dist/src/vm/proc.d.ts +94 -0
- package/dist/src/vm/proc.d.ts.map +1 -0
- package/dist/src/vm/proc.js +130 -0
- package/dist/src/vm/proc.js.map +1 -0
- package/dist/src/vm/state.d.ts +104 -0
- package/dist/src/vm/state.d.ts.map +1 -0
- package/dist/src/vm/state.js +233 -0
- package/dist/src/vm/state.js.map +1 -0
- package/dist/src/vm/test.d.ts +11 -0
- package/dist/src/vm/test.d.ts.map +1 -0
- package/dist/src/vm/test.js +277 -0
- package/dist/src/vm/test.js.map +1 -0
- package/package.json +63 -4
- package/src/analysis/analyze.ts +1395 -0
- package/src/analysis/fs.ts +121 -0
- package/src/analysis/outputs.ts +242 -0
- package/src/analysis/value.ts +264 -0
- package/src/behaviors/defs/core.ts +970 -0
- package/src/behaviors/defs/devtools.ts +400 -0
- package/src/behaviors/defs/macos.ts +426 -0
- package/src/behaviors/defs/tools.ts +596 -0
- package/src/behaviors/dsl.ts +293 -0
- package/src/behaviors/index.ts +19 -0
- package/src/behaviors/registry.ts +261 -0
- package/src/behaviors/roles.ts +187 -0
- package/src/behaviors/types.ts +127 -0
- package/src/index.ts +27 -0
- package/src/integrations/deepsea.ts +259 -0
- package/src/machine.ts +483 -0
- package/src/parser/ast.ts +472 -0
- package/src/parser/parser.ts +1826 -0
- package/src/policy/effects.ts +102 -0
- package/src/policy/policy.ts +578 -0
- package/src/printer/doc.ts +454 -0
- package/src/printer/highlight.ts +283 -0
- package/src/printer/html.ts +441 -0
- package/src/printer/print.ts +528 -0
- package/src/printer/summary.ts +601 -0
- package/src/trace/events.ts +102 -0
- package/src/trace/progress.ts +119 -0
- package/src/vm/arith.ts +236 -0
- package/src/vm/builtins.ts +1590 -0
- package/src/vm/emulate.ts +337 -0
- package/src/vm/expand.ts +721 -0
- package/src/vm/external.ts +265 -0
- package/src/vm/fifo.ts +226 -0
- package/src/vm/interp.ts +1623 -0
- package/src/vm/io.ts +570 -0
- package/src/vm/pattern.ts +477 -0
- package/src/vm/proc.ts +147 -0
- package/src/vm/state.ts +267 -0
- package/src/vm/test.ts +253 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavior definitions for macOS system tools (most also describe their BSD
|
|
3
|
+
* or Linux namesakes well enough): preferences and property lists, launchd,
|
|
4
|
+
* code signing, network configuration, Spotlight, disk images, diagnostics.
|
|
5
|
+
*
|
|
6
|
+
* Query forms are modeled precisely; forms that change system state the
|
|
7
|
+
* policy has no vocabulary for (network settings, services, firewall) are
|
|
8
|
+
* `unknown` with `complete: false`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AValue } from '../../analysis/value.ts'
|
|
12
|
+
import { concat, concrete, hole, known, knownPrefix } from '../../analysis/value.ts'
|
|
13
|
+
import type { Effect } from '../../policy/effects.ts'
|
|
14
|
+
import { define, type Def } from '../dsl.ts'
|
|
15
|
+
import type { Behavior, BehaviorHelpers, Invocation } from '../types.ts'
|
|
16
|
+
|
|
17
|
+
const args = (inv: Invocation): AValue[] => inv.argv.slice(1)
|
|
18
|
+
const isDash = (v: AValue): boolean => concrete(v) === '-'
|
|
19
|
+
|
|
20
|
+
const QUERY: Def = { anyFlags: true, operands: 'arg*', stdin: false }
|
|
21
|
+
const opaque = (detail: string): Def => ({ anyFlags: true, operands: 'arg*', opaque: detail, stdin: 'maybe' })
|
|
22
|
+
|
|
23
|
+
/** Commands that only print system information. */
|
|
24
|
+
function info(names: string[], summary: string): Behavior {
|
|
25
|
+
return { names, summary, analyze: () => ({ effects: [], readsStdin: false }) }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A tool whose first word decides: query words are read-only, others change system state. */
|
|
29
|
+
function queryOrChange(names: string[], summary: string, query: RegExp, change: string): Behavior {
|
|
30
|
+
return {
|
|
31
|
+
names,
|
|
32
|
+
summary,
|
|
33
|
+
analyze(inv, h) {
|
|
34
|
+
const a = args(inv)
|
|
35
|
+
const first = a.find((x) => knownPrefix(x) !== '' && !/^-(n|v|q)$/.test(concrete(x) ?? ''))
|
|
36
|
+
const w = first === undefined ? '' : concrete(first)
|
|
37
|
+
if (w === undefined) return { effects: [{ kind: 'unknown', detail: `${inv.name} with unknown arguments` }], readsStdin: false, complete: false }
|
|
38
|
+
if (first && /^[a-z]/.test(w)) h.role(first, 'subcommand')
|
|
39
|
+
if (query.test(w)) return { effects: [], readsStdin: false }
|
|
40
|
+
return { effects: [{ kind: 'unknown', detail: `${inv.name} ${w}: ${change}` }], readsStdin: false, complete: false }
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const home = (h: BehaviorHelpers, rest: string): AValue => concat(h.home() ?? known('~'), known(rest))
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// preferences and property lists
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
/** defaults [-currentHost | -host H] VERB [DOMAIN] …; a domain may be a plist path. */
|
|
52
|
+
const defaults: Behavior = {
|
|
53
|
+
names: ['defaults'],
|
|
54
|
+
summary: 'user preferences',
|
|
55
|
+
analyze(inv, h) {
|
|
56
|
+
const a = [...args(inv)]
|
|
57
|
+
while (a.length > 0 && /^-(currentHost|host)$/.test(concrete(a[0]) ?? '')) a.splice(0, concrete(a[0]) === '-host' ? 2 : 1)
|
|
58
|
+
const verb = concrete(a[0] ?? known('')) ?? '?'
|
|
59
|
+
if (a[0]) h.role(a[0], 'subcommand')
|
|
60
|
+
const rest = a.slice(1).filter((x) => !/^-(g|globalDomain|app)$/.test(concrete(x) ?? ''))
|
|
61
|
+
const domain = rest[0]
|
|
62
|
+
const prefs = home(h, '/Library/Preferences')
|
|
63
|
+
// a domain given as a path names that plist (without .plist)
|
|
64
|
+
const target = (): AValue => {
|
|
65
|
+
if (!domain || !/^[/~.]/.test(knownPrefix(domain))) return prefs
|
|
66
|
+
const s = concrete(domain)
|
|
67
|
+
return s !== undefined && !s.endsWith('.plist') ? h.resolve(known(s + '.plist')) : h.resolve(domain)
|
|
68
|
+
}
|
|
69
|
+
const isPrefs = (v: AValue): boolean => v === prefs
|
|
70
|
+
if (/^(read|read-type|find|domains|help)$/.test(verb)) {
|
|
71
|
+
const t = target()
|
|
72
|
+
return { effects: [{ kind: 'read', path: t, ...(isPrefs(t) ? { recursive: true } : {}) }], readsStdin: false }
|
|
73
|
+
}
|
|
74
|
+
if (verb === 'export') {
|
|
75
|
+
const t = target()
|
|
76
|
+
const out = rest[1]
|
|
77
|
+
const effects: Effect[] = [{ kind: 'read', path: t, ...(isPrefs(t) ? { recursive: true } : {}) }]
|
|
78
|
+
if (out && !isDash(out)) effects.push({ kind: 'write', path: h.resolve(out) })
|
|
79
|
+
return { effects, readsStdin: false }
|
|
80
|
+
}
|
|
81
|
+
if (/^(write|delete|rename|import)$/.test(verb)) {
|
|
82
|
+
const t = target()
|
|
83
|
+
const effects: Effect[] = [{ kind: 'write', path: t, ...(isPrefs(t) ? { recursive: true } : {}) }]
|
|
84
|
+
if (verb === 'import' && rest[1]) {
|
|
85
|
+
if (isDash(rest[1])) return { effects, readsStdin: true }
|
|
86
|
+
effects.push({ kind: 'read', path: h.resolve(rest[1]) })
|
|
87
|
+
}
|
|
88
|
+
return { effects, readsStdin: false }
|
|
89
|
+
}
|
|
90
|
+
return { effects: [{ kind: 'unknown', detail: `defaults ${verb}` }], readsStdin: false, complete: false }
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** plutil: -p/-lint/-type read; -convert/-extract/-insert/-replace/-remove rewrite in place unless -o. */
|
|
95
|
+
const PLUTIL_MODES: Record<string, number> = { '-p': 0, '-lint': 0, '-help': 0, '-type': 1, '-convert': 1, '-extract': 2, '-insert': 3, '-replace': 3, '-remove': 1, '-create': 1 }
|
|
96
|
+
const plutil: Behavior = {
|
|
97
|
+
names: ['plutil'],
|
|
98
|
+
summary: 'property list utility',
|
|
99
|
+
analyze(inv, h) {
|
|
100
|
+
const a = args(inv)
|
|
101
|
+
let mode = '-lint'
|
|
102
|
+
let out: AValue | undefined
|
|
103
|
+
let ext: AValue | undefined
|
|
104
|
+
const files: AValue[] = []
|
|
105
|
+
let complete = true
|
|
106
|
+
for (let i = 0; i < a.length; i++) {
|
|
107
|
+
const s = concrete(a[i])
|
|
108
|
+
if (s === undefined) {
|
|
109
|
+
files.push(a[i])
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
if (s in PLUTIL_MODES) {
|
|
113
|
+
mode = s
|
|
114
|
+
// `-insert KEY -TYPE VALUE`, `-extract KEYPATH FMT`, `-convert FMT`
|
|
115
|
+
i += PLUTIL_MODES[s]
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
if (s === '-o') out = a[++i]
|
|
119
|
+
else if (s === '-e') ext = a[++i]
|
|
120
|
+
else if (s === '-s' || s === '-r' || s === '-n' || s === '--' || s === '-x' || s === '-json' || s === '-xml1' || s === '-binary1' || s === '-raw' || s === '-expect') {
|
|
121
|
+
if (s === '-expect') i++
|
|
122
|
+
} else if (s.startsWith('-') && s !== '-') complete = false
|
|
123
|
+
else files.push(a[i])
|
|
124
|
+
}
|
|
125
|
+
const effects: Effect[] = []
|
|
126
|
+
let readsStdin = false
|
|
127
|
+
for (const f of files) {
|
|
128
|
+
if (isDash(f)) readsStdin = true
|
|
129
|
+
else effects.push({ kind: 'read', path: h.resolve(f) })
|
|
130
|
+
}
|
|
131
|
+
if (!['-p', '-lint', '-help', '-type'].includes(mode)) {
|
|
132
|
+
if (out) {
|
|
133
|
+
if (!isDash(out)) effects.push({ kind: 'write', path: h.resolve(out) })
|
|
134
|
+
} else if (ext) {
|
|
135
|
+
for (const f of files) if (!isDash(f)) effects.push({ kind: 'write', path: concat(h.resolve(f), known('.'), ext) })
|
|
136
|
+
} else for (const f of files) if (!isDash(f)) effects.push({ kind: 'write', path: h.resolve(f) })
|
|
137
|
+
}
|
|
138
|
+
return { effects, readsStdin, complete }
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** PlistBuddy [-x] [-c CMD]... FILE */
|
|
143
|
+
const plistBuddy: Behavior = {
|
|
144
|
+
names: ['PlistBuddy'],
|
|
145
|
+
summary: 'edit property lists',
|
|
146
|
+
analyze(inv, h) {
|
|
147
|
+
const a = args(inv)
|
|
148
|
+
const cmds: AValue[] = []
|
|
149
|
+
let file: AValue | undefined
|
|
150
|
+
for (let i = 0; i < a.length; i++) {
|
|
151
|
+
const s = concrete(a[i])
|
|
152
|
+
if (s === '-c') {
|
|
153
|
+
if (a[i + 1]) h.role(a[i + 1], 'code')
|
|
154
|
+
cmds.push(a[++i])
|
|
155
|
+
} else if (s === '-x' || s === '-h') continue
|
|
156
|
+
else file = a[i]
|
|
157
|
+
}
|
|
158
|
+
if (!file) return { effects: [], readsStdin: false }
|
|
159
|
+
const path = h.resolve(file)
|
|
160
|
+
if (cmds.length === 0) return { effects: [{ kind: 'read', path }, { kind: 'write', path, maybe: true }, { kind: 'unknown', detail: 'interactive PlistBuddy commands' }], readsStdin: true, complete: false }
|
|
161
|
+
const effects: Effect[] = [{ kind: 'read', path }]
|
|
162
|
+
let complete = true
|
|
163
|
+
let writes = false
|
|
164
|
+
for (const c of cmds) {
|
|
165
|
+
const t = c ? concrete(c) : undefined
|
|
166
|
+
if (t === undefined) {
|
|
167
|
+
complete = false
|
|
168
|
+
writes = true
|
|
169
|
+
continue
|
|
170
|
+
}
|
|
171
|
+
const verb = t.trim().split(/\s+/)[0]?.toLowerCase() ?? ''
|
|
172
|
+
if (verb === 'print' || verb === 'help' || verb === 'exit') continue
|
|
173
|
+
if (verb === 'merge' || verb === 'import') complete = false // reads another file
|
|
174
|
+
writes = true
|
|
175
|
+
}
|
|
176
|
+
if (writes) effects.push({ kind: 'write', path, ...(complete ? {} : { maybe: true }) })
|
|
177
|
+
return { effects, readsStdin: false, complete }
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// launchd, Xcode, signing
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
const launchctl = queryOrChange(['launchctl'], 'launchd services',
|
|
186
|
+
/^(print|print-cache|print-disabled|list|blame|procinfo|hostinfo|examine|dumpstate|dumpjpcategory|managerpid|manageruid|managername|getenv|version|help|error|resolveport|plist)$/, 'starts, stops or configures services')
|
|
187
|
+
|
|
188
|
+
const xcodeSelect = define({
|
|
189
|
+
names: ['xcode-select'], summary: 'active developer directory',
|
|
190
|
+
spec: { short: 'hpvr', shortWithArg: 's', long: { help: false, 'print-path': false, version: false, switch: '=s', reset: '=r', install: false } },
|
|
191
|
+
stdin: false, stdout: { type: 'path' },
|
|
192
|
+
when: { 's|r|install': { opaque: 'xcode-select changes the developer directory or installs tools' } },
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
/** xcrun [--sdk X] [--toolchain X] TOOL ARGS… | --find TOOL | --show-sdk-*. */
|
|
196
|
+
const xcrun: Behavior = {
|
|
197
|
+
names: ['xcrun'],
|
|
198
|
+
summary: 'run or locate developer tools',
|
|
199
|
+
analyze(inv, h) {
|
|
200
|
+
const a = args(inv)
|
|
201
|
+
let i = 0
|
|
202
|
+
for (; i < a.length; i++) {
|
|
203
|
+
const s = concrete(a[i])
|
|
204
|
+
if (s === undefined) return { effects: [{ kind: 'unknown', detail: 'xcrun with unknown arguments' }], readsStdin: 'maybe', complete: false }
|
|
205
|
+
if (s === '--sdk' || s === '-sdk' || s === '--toolchain' || s === '-toolchain') {
|
|
206
|
+
i++
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
if (/^-(-)?(find|f)$/.test(s) || /^--show-sdk-|^--show-toolchain|^-(-)?(version|help|h|kill-cache|k)$/.test(s)) return { effects: [], readsStdin: false, stdout: { type: 'path' } }
|
|
210
|
+
if (/^-(-)?(log|verbose|no-cache|l|v|n|r|run)$/.test(s)) continue
|
|
211
|
+
break
|
|
212
|
+
}
|
|
213
|
+
if (i >= a.length) return { effects: [], readsStdin: false }
|
|
214
|
+
return h.subcommand(a.slice(i))
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** codesign: -s/--sign rewrites the operands; -v/-d/--verify/--display read them. */
|
|
219
|
+
const codesign = define({
|
|
220
|
+
names: ['codesign'], summary: 'code signing',
|
|
221
|
+
spec: { short: 'dvfhRoV', shortWithArg: 'siPr', long: { sign: '=s', verify: false, display: false, deep: false, strict: true, force: false, verbose: true, identifier: '=i', entitlements: true, options: true, timestamp: true, 'preserve-metadata': true, keychain: true, requirements: '=r', remove: false, 'remove-signature': false, xml: false, der: false, 'check-notarization': false, 'generate-entitlement-der': false, 'all-architectures': false, prefix: true, 'test-requirement': true, detached: true, bundle: true, file: false, extract: true } },
|
|
222
|
+
operands: 'read*', stdin: false, flags: { entitlements: 'write' },
|
|
223
|
+
when: { 's|remove-signature|remove': { operands: 'write*', flags: { entitlements: 'read', keychain: 'read' } } },
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
const spctl = queryOrChange(['spctl'], 'Gatekeeper assessments', /^(-a|--assess|--status|-s|--test|-t|--list|--help|-h)$/, 'changes the Gatekeeper policy')
|
|
227
|
+
|
|
228
|
+
const security: Behavior = {
|
|
229
|
+
names: ['security'],
|
|
230
|
+
summary: 'keychains and certificates',
|
|
231
|
+
analyze(inv, h) {
|
|
232
|
+
const a = args(inv)
|
|
233
|
+
const sub = concrete(a.find((x) => !knownPrefix(x).startsWith('-')) ?? known('')) ?? '?'
|
|
234
|
+
const keychains = home(h, '/Library/Keychains')
|
|
235
|
+
if (/^(find-|dump-|list-|show-|verify-cert$|error$|help$)/.test(sub) || (sub === 'cms' && a.some((x) => concrete(x) === '-D'))) {
|
|
236
|
+
const effects: Effect[] = [{ kind: 'read', path: keychains, recursive: true }, { kind: 'read', path: known('/Library/Keychains'), recursive: true, maybe: true }]
|
|
237
|
+
if (sub === 'cms') for (let i = 0; i < a.length; i++) if (concrete(a[i]) === '-i' && a[i + 1]) effects.push({ kind: 'read', path: h.resolve(a[i + 1]) })
|
|
238
|
+
return { effects, readsStdin: sub === 'cms' && !a.some((x) => concrete(x) === '-i') }
|
|
239
|
+
}
|
|
240
|
+
return { effects: [{ kind: 'write', path: keychains, recursive: true, maybe: true }, { kind: 'unknown', detail: `security ${sub}` }], readsStdin: 'maybe', complete: false }
|
|
241
|
+
},
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// network
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
const networkTools: Behavior[] = [
|
|
249
|
+
queryOrChange(['ipconfig'], 'network interfaces (macOS)', /^(getifaddr|getoption|getpacket|getsummary|getv6packet|ifcount|getiflist|getra|waitall|help)$/, 'changes interface configuration'),
|
|
250
|
+
{
|
|
251
|
+
names: ['ifconfig'],
|
|
252
|
+
summary: 'network interfaces',
|
|
253
|
+
analyze(inv) {
|
|
254
|
+
// `ifconfig`, `ifconfig -a`, `ifconfig -l`, `ifconfig en0`: queries; more words configure the interface
|
|
255
|
+
const ops = args(inv).filter((x) => !/^-[aludLmvrC]+$/.test(concrete(x) ?? ''))
|
|
256
|
+
if (ops.length <= 1) return { effects: [], readsStdin: false }
|
|
257
|
+
return { effects: [{ kind: 'unknown', detail: 'ifconfig configures an interface' }], readsStdin: false, complete: false }
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
names: ['arp', 'ndp'],
|
|
262
|
+
summary: 'address resolution tables',
|
|
263
|
+
analyze(inv) {
|
|
264
|
+
const a = args(inv).map((x) => concrete(x))
|
|
265
|
+
if (a.some((x) => x === undefined || /^-[a-zA-Z]*[dsSf]/.test(x))) return { effects: [{ kind: 'unknown', detail: `${inv.name} changes the table` }], readsStdin: false, complete: false }
|
|
266
|
+
return { effects: [], readsStdin: false }
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
queryOrChange(['route'], 'routing tables', /^(get|monitor|show|-n)$/, 'changes routes'),
|
|
270
|
+
queryOrChange(['scutil'], 'system configuration', /^(--dns|--proxy|--nwi|--get|-r|-w)$/, 'changes the system configuration'),
|
|
271
|
+
queryOrChange(['networksetup'], 'network settings', /^-(get|list|print|version|help|isBluetooth|show)/, 'changes network settings'),
|
|
272
|
+
queryOrChange(['socketfilterfw'], 'application firewall', /^--(get|list)/, 'changes the firewall'),
|
|
273
|
+
{
|
|
274
|
+
names: ['dscacheutil'],
|
|
275
|
+
summary: 'directory service cache',
|
|
276
|
+
analyze: (inv) => ({ effects: args(inv).some((x) => concrete(x) === '-flushcache') ? [{ kind: 'process', detail: 'flush the DNS cache' }] : [], readsStdin: false }),
|
|
277
|
+
},
|
|
278
|
+
define({ names: ['dns-sd', 'mDNS'], summary: 'Bonjour browsing and registration', anyFlags: true, operands: 'arg*', stdin: false, effects: [{ kind: 'network', detail: 'multicast DNS' }] }),
|
|
279
|
+
define({ names: ['mtr', 'whois'], summary: 'network diagnostics', anyFlags: true, operands: 'url arg*', stdin: false }),
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// system state and diagnostics
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
const systemTools: Behavior[] = [
|
|
287
|
+
info(['system_profiler', 'ioreg', 'lsappinfo', 'footprint', 'vmmap', 'heap', 'leaks', 'fs_usage', 'powermetrics', 'nettop', 'csrutil', 'kextstat', 'machine'], 'print system or process information'),
|
|
288
|
+
define({
|
|
289
|
+
names: ['sysctl'], summary: 'kernel state',
|
|
290
|
+
spec: { short: 'abdehiNnoqxAX', shortWithArg: 'f' }, operands: 'arg*', stdin: false,
|
|
291
|
+
// `name=value` and -w set kernel parameters
|
|
292
|
+
extra: (r, { operands, p }) => (p.flags.has('w') || operands.some((o) => knownPrefix(o).includes('=') || concrete(o) === undefined) ? { ...r, effects: [...r.effects, { kind: 'unknown', detail: 'sysctl sets kernel parameters' }], complete: false } : r),
|
|
293
|
+
}),
|
|
294
|
+
define({
|
|
295
|
+
names: ['memory_pressure'], summary: 'memory pressure', spec: { short: 'Q', shortWithArg: 'lSps' }, stdin: false,
|
|
296
|
+
when: { S: { opaque: 'memory_pressure -S simulates memory pressure' } },
|
|
297
|
+
}),
|
|
298
|
+
queryOrChange(['pmset'], 'power management', /^-g$/, 'changes power settings'),
|
|
299
|
+
queryOrChange(['tmutil'], 'Time Machine', /^(listbackups|latestbackup|status|destinationinfo|isexcluded|machinedirectory|listlocalsnapshots|listlocalsnapshotdates|compare|calculatedrift|uniquesize|version|help|verifychecksums)$/, 'changes backups or their settings'),
|
|
300
|
+
queryOrChange(['diskutil'], 'disks and volumes', /^(list|info|information|activity|listFilesystems|apfs|cs|appleRAID|verifyVolume|verifyDisk|image)$/, 'changes disks or volumes'),
|
|
301
|
+
queryOrChange(['hdiutil'], 'disk images', /^(info|imageinfo|verify|checksum|isencrypted|plugins|help|pmap)$/, 'mounts or creates disk images'),
|
|
302
|
+
queryOrChange(['log'], 'unified logging', /^(show|stream|stats|help)$/, 'changes logging configuration or collects archives'),
|
|
303
|
+
queryOrChange(['lsregister'], 'Launch Services database', /^-(dump|h|help)$/, 'changes the Launch Services database'),
|
|
304
|
+
{
|
|
305
|
+
names: ['sample', 'spindump'],
|
|
306
|
+
summary: 'sample a process',
|
|
307
|
+
analyze(inv, h) {
|
|
308
|
+
// writes the report to -file PATH, else into /tmp
|
|
309
|
+
const a = args(inv)
|
|
310
|
+
const i = a.findIndex((x) => concrete(x) === '-file' || concrete(x) === '-o')
|
|
311
|
+
const out: AValue = i >= 0 && a[i + 1] ? h.resolve(a[i + 1]) : concat(known('/tmp/'), hole('filename', 'sample report'))
|
|
312
|
+
return { effects: [{ kind: 'write', path: out }], readsStdin: false }
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// files, Spotlight, images, screen
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
const mdfind: Behavior = {
|
|
322
|
+
names: ['mdfind'],
|
|
323
|
+
summary: 'Spotlight search',
|
|
324
|
+
analyze(inv, h) {
|
|
325
|
+
const a = args(inv)
|
|
326
|
+
const effects: Effect[] = []
|
|
327
|
+
let complete = true
|
|
328
|
+
for (let i = 0; i < a.length; i++) {
|
|
329
|
+
const s = concrete(a[i])
|
|
330
|
+
if (s === '-onlyin' && a[i + 1]) {
|
|
331
|
+
h.role(a[i + 1], 'dir')
|
|
332
|
+
effects.push({ kind: 'read', path: h.resolve(a[++i]), recursive: true })
|
|
333
|
+
} else if (s === '-name' || s === '-attr' || s === '-s') {
|
|
334
|
+
if (a[i + 1]) h.role(a[i + 1], 'pattern')
|
|
335
|
+
i++
|
|
336
|
+
} else if (s === '-live' || s === '-count' || s === '-0' || s === '-literal' || s === '-interpret' || s === '-reprint') continue
|
|
337
|
+
else if (s !== undefined && s.startsWith('-')) complete = false
|
|
338
|
+
else h.role(a[i], 'pattern')
|
|
339
|
+
}
|
|
340
|
+
// names (not contents) of matching files anywhere in the index
|
|
341
|
+
if (!effects.length) effects.push({ kind: 'read', path: known('/'), recursive: true, maybe: true })
|
|
342
|
+
return { effects, readsStdin: false, complete, stdout: { type: 'path', lines: true } }
|
|
343
|
+
},
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const mdls = define({
|
|
347
|
+
names: ['mdls'], summary: 'Spotlight metadata',
|
|
348
|
+
spec: { longSingle: true, long: { name: true, raw: false, nullMarker: true, plist: true, r: false } },
|
|
349
|
+
operands: 'read*', flags: { plist: 'write' }, stdin: false,
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
/** sips: queries (-g) read; anything else rewrites the images, or writes --out. */
|
|
353
|
+
const SIPS_QUERY = new Set(['g', 'getProperty', '1', 'oneLine', 'h', 'help', 'H', 'helpProperties', 'v', 'version', 'debug'])
|
|
354
|
+
const sips = define({
|
|
355
|
+
names: ['sips'], summary: 'image processing (macOS)',
|
|
356
|
+
spec: {
|
|
357
|
+
short: '1hHv', shortWithArg: 'gsdrfcpzZioeEmMxj', arity: { s: 2, c: 2, p: 2, z: 2, M: 2 },
|
|
358
|
+
long: { getProperty: '=g', setProperty: '=s', deleteProperty: '=d', rotate: '=r', flip: '=f', cropToHeightWidth: '=c', padToHeightWidth: '=p', resampleHeightWidth: '=z', resampleHeightWidthMax: '=Z', resampleWidth: true, resampleHeight: true, cropOffset: 2, padColor: true, addIcon: '=i', out: '=o', embedProfile: '=e', embedProfileIfNone: '=E', matchTo: '=m', matchToWithIntent: '=M', extractProfile: '=x', extractTag: 2, js: '=j', oneLine: '=1', help: '=h', helpProperties: '=H', version: '=v', debug: false, optimizeColorForSharing: false, verifyColor: false, deleteColorManagementProperties: false, setColorManagementPropertiesFromFile: true },
|
|
359
|
+
},
|
|
360
|
+
operands: 'read*', stdin: false, flags: { e: 'read', E: 'read', m: 'read', x: 'write', extractTag: 'write' },
|
|
361
|
+
extra: (r, { h, p, operands }) => {
|
|
362
|
+
if (p.flags.has('j')) return { ...r, effects: [...r.effects, { kind: 'unknown', detail: 'sips runs a JavaScript file' }], complete: false }
|
|
363
|
+
const modifies = [...p.flags.keys()].some((k) => !SIPS_QUERY.has(k) && !['e', 'E', 'm', 'M', 'x', 'extractTag', 'o'].includes(k)) || ['e', 'E', 'm', 'M'].some((k) => p.flags.has(k))
|
|
364
|
+
if (!modifies) return r
|
|
365
|
+
const out = p.flags.get('o')
|
|
366
|
+
const writes: Effect[] = out && out !== true
|
|
367
|
+
? [{ kind: 'write', path: h.resolve(out), ...(operands.length > 1 ? { recursive: true } : {}) }]
|
|
368
|
+
: operands.map((o): Effect => ({ kind: 'write', path: h.resolve(o) }))
|
|
369
|
+
if (out && out !== true && operands.length > 1) h.role(out, 'dir')
|
|
370
|
+
return { ...r, effects: [...r.effects, ...writes] }
|
|
371
|
+
},
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
const screencapture = define({
|
|
375
|
+
names: ['screencapture'], summary: 'screenshots (macOS)',
|
|
376
|
+
spec: { short: 'cCdimMoPswWxSaUuvkgbBIrpGJ', shortWithArg: 'RtTlDVBGJf' },
|
|
377
|
+
operands: 'write*', stdin: false,
|
|
378
|
+
when: { c: { operands: 'arg*' }, 'M|P': { effects: [{ kind: 'process', detail: 'opens the capture in an app' }] } },
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
const qlmanage = define({
|
|
382
|
+
names: ['qlmanage'], summary: 'Quick Look', spec: { short: 'pthmrxi', shortWithArg: 'osgc', long: {} },
|
|
383
|
+
operands: 'read*', stdin: false, flags: { o: 'write:dir' },
|
|
384
|
+
when: { p: { opaque: 'qlmanage -p opens a preview window' }, r: { effects: [{ kind: 'process', detail: 'resets Quick Look' }] } },
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
/** Chrome / Chromium: --headless with an output flag renders a page; anything else is the GUI browser. */
|
|
388
|
+
const chrome: Behavior = {
|
|
389
|
+
names: ['Google Chrome', 'google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', 'chrome', 'Chromium', 'Google Chrome Canary', 'Microsoft Edge', 'Brave Browser'],
|
|
390
|
+
summary: 'web browser',
|
|
391
|
+
analyze(inv, h) {
|
|
392
|
+
const a = args(inv)
|
|
393
|
+
const opt = (name: string): AValue | true | undefined => {
|
|
394
|
+
for (const x of a) {
|
|
395
|
+
const s = concrete(x)
|
|
396
|
+
if (s === `--${name}`) return true
|
|
397
|
+
if (s?.startsWith(`--${name}=`)) return known(s.slice(name.length + 3))
|
|
398
|
+
}
|
|
399
|
+
return undefined
|
|
400
|
+
}
|
|
401
|
+
if (opt('version') || opt('help')) return { effects: [], readsStdin: false }
|
|
402
|
+
const headless = opt('headless') !== undefined
|
|
403
|
+
const effects: Effect[] = [{ kind: 'network', detail: inv.name, maybe: true }]
|
|
404
|
+
const shot = opt('screenshot')
|
|
405
|
+
const pdf = opt('print-to-pdf')
|
|
406
|
+
const profile = opt('user-data-dir')
|
|
407
|
+
effects.push(profile && profile !== true
|
|
408
|
+
? { kind: 'write', path: h.resolve(profile), recursive: true }
|
|
409
|
+
: { kind: 'write', path: home(h, process.platform === 'darwin' ? '/Library/Application Support' : '/.config'), recursive: true, maybe: true })
|
|
410
|
+
if (shot) effects.push({ kind: 'write', path: shot === true ? concat(inv.cwd, known('/screenshot.png')) : h.resolve(shot) })
|
|
411
|
+
if (pdf) effects.push({ kind: 'write', path: pdf === true ? concat(inv.cwd, known('/output.pdf')) : h.resolve(pdf) })
|
|
412
|
+
for (const x of a) {
|
|
413
|
+
const s = concrete(x)
|
|
414
|
+
if (s !== undefined && !s.startsWith('-') && !/^[a-z]+:/.test(s)) effects.push({ kind: 'read', path: h.resolve(x) })
|
|
415
|
+
if (s?.startsWith('file://')) effects.push({ kind: 'read', path: known(decodeURIComponent(s.slice(7))) })
|
|
416
|
+
}
|
|
417
|
+
const outputOnly = headless && (shot || pdf || opt('dump-dom'))
|
|
418
|
+
if (!outputOnly) effects.push({ kind: 'unknown', detail: `${inv.name} runs a browser` })
|
|
419
|
+
return { effects, readsStdin: false, complete: !!outputOnly && !opt('remote-debugging-port') && !opt('remote-debugging-pipe') }
|
|
420
|
+
},
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export const macosBehaviors: Behavior[] = [
|
|
424
|
+
defaults, plutil, plistBuddy, launchctl, xcodeSelect, xcrun, codesign, spctl, security,
|
|
425
|
+
...networkTools, ...systemTools, mdfind, mdls, sips, screencapture, qlmanage, chrome,
|
|
426
|
+
]
|