@soku-ai/cli 0.1.0-alpha.1 → 0.1.0-alpha.11

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.
Files changed (92) hide show
  1. package/dist/auth/device.d.ts +11 -0
  2. package/dist/auth/device.d.ts.map +1 -1
  3. package/dist/auth/device.js +17 -1
  4. package/dist/auth/device.js.map +1 -1
  5. package/dist/commands/ads.d.ts +32 -0
  6. package/dist/commands/ads.d.ts.map +1 -0
  7. package/dist/commands/ads.js +752 -0
  8. package/dist/commands/ads.js.map +1 -0
  9. package/dist/commands/auth.d.ts +2 -0
  10. package/dist/commands/auth.d.ts.map +1 -1
  11. package/dist/commands/auth.js +28 -7
  12. package/dist/commands/auth.js.map +1 -1
  13. package/dist/commands/automation.d.ts +80 -0
  14. package/dist/commands/automation.d.ts.map +1 -0
  15. package/dist/commands/automation.js +213 -0
  16. package/dist/commands/automation.js.map +1 -0
  17. package/dist/commands/brand-skill.d.ts +72 -0
  18. package/dist/commands/brand-skill.d.ts.map +1 -0
  19. package/dist/commands/brand-skill.js +351 -0
  20. package/dist/commands/brand-skill.js.map +1 -0
  21. package/dist/commands/brand.d.ts.map +1 -1
  22. package/dist/commands/brand.js +59 -1
  23. package/dist/commands/brand.js.map +1 -1
  24. package/dist/commands/call.d.ts.map +1 -1
  25. package/dist/commands/call.js +6 -3
  26. package/dist/commands/call.js.map +1 -1
  27. package/dist/commands/context.d.ts +23 -0
  28. package/dist/commands/context.d.ts.map +1 -0
  29. package/dist/commands/context.js +291 -0
  30. package/dist/commands/context.js.map +1 -0
  31. package/dist/commands/files.d.ts +10 -0
  32. package/dist/commands/files.d.ts.map +1 -0
  33. package/dist/commands/files.js +48 -0
  34. package/dist/commands/files.js.map +1 -0
  35. package/dist/commands/generated.d.ts +24 -3
  36. package/dist/commands/generated.d.ts.map +1 -1
  37. package/dist/commands/generated.js +97 -10
  38. package/dist/commands/generated.js.map +1 -1
  39. package/dist/commands/memory.d.ts +18 -0
  40. package/dist/commands/memory.d.ts.map +1 -0
  41. package/dist/commands/memory.js +70 -0
  42. package/dist/commands/memory.js.map +1 -0
  43. package/dist/commands/review.d.ts.map +1 -1
  44. package/dist/commands/review.js +46 -2
  45. package/dist/commands/review.js.map +1 -1
  46. package/dist/commands/seo-hosting.d.ts +107 -0
  47. package/dist/commands/seo-hosting.d.ts.map +1 -0
  48. package/dist/commands/seo-hosting.js +499 -0
  49. package/dist/commands/seo-hosting.js.map +1 -0
  50. package/dist/commands/skill.d.ts +50 -6
  51. package/dist/commands/skill.d.ts.map +1 -1
  52. package/dist/commands/skill.js +120 -56
  53. package/dist/commands/skill.js.map +1 -1
  54. package/dist/commands/update.d.ts +23 -0
  55. package/dist/commands/update.d.ts.map +1 -0
  56. package/dist/commands/update.js +345 -0
  57. package/dist/commands/update.js.map +1 -0
  58. package/dist/commands/workspace.d.ts +4 -0
  59. package/dist/commands/workspace.d.ts.map +1 -0
  60. package/dist/commands/workspace.js +132 -0
  61. package/dist/commands/workspace.js.map +1 -0
  62. package/dist/config.d.ts +5 -4
  63. package/dist/config.d.ts.map +1 -1
  64. package/dist/config.js +5 -7
  65. package/dist/config.js.map +1 -1
  66. package/dist/generated/capabilities.json +291 -30
  67. package/dist/http/client.d.ts.map +1 -1
  68. package/dist/http/client.js +74 -16
  69. package/dist/http/client.js.map +1 -1
  70. package/dist/index.js +35 -3
  71. package/dist/index.js.map +1 -1
  72. package/dist/output/envelope.d.ts +5 -0
  73. package/dist/output/envelope.d.ts.map +1 -1
  74. package/dist/output/envelope.js +101 -2
  75. package/dist/output/envelope.js.map +1 -1
  76. package/dist/output/unwrap.d.ts.map +1 -1
  77. package/dist/output/unwrap.js +18 -1
  78. package/dist/output/unwrap.js.map +1 -1
  79. package/dist/skills/unzip.d.ts +1 -1
  80. package/dist/skills/unzip.js +1 -1
  81. package/dist/update-check.d.ts +0 -1
  82. package/dist/update-check.d.ts.map +1 -1
  83. package/dist/update-check.js +4 -54
  84. package/dist/update-check.js.map +1 -1
  85. package/dist/version.d.ts +1 -1
  86. package/dist/version.d.ts.map +1 -1
  87. package/dist/version.js +1 -1
  88. package/dist/version.js.map +1 -1
  89. package/package.json +7 -4
  90. package/postinstall.cjs +85 -0
  91. package/skills/soku/SKILL.md +402 -32
  92. package/README.md +0 -91
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,iBAAiB,CAAA;AAC9C,eAAO,MAAM,WAAW,kBAAkB,CAAA"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,iBAAiB,CAAA;AAC9C,eAAO,MAAM,WAAW,mBAAmB,CAAA"}
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export const CLI_PACKAGE_NAME = '@soku-ai/cli';
2
- export const CLI_VERSION = '0.1.0-alpha.1';
2
+ export const CLI_VERSION = '0.1.0-alpha.11';
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAA;AAC9C,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAA"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAA;AAC9C,MAAM,CAAC,MAAM,WAAW,GAAG,gBAAgB,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@soku-ai/cli",
3
- "version": "0.1.0-alpha.1",
4
- "description": "Soku CLI — call Soku ads/GA4 data capabilities from any AI agent or shell.",
3
+ "version": "0.1.0-alpha.11",
4
+ "description": "Soku CLI — call Soku ads/GA4/PostHog data capabilities from any AI agent or shell.",
5
5
  "license": "Proprietary",
6
6
  "author": "Soku",
7
7
  "homepage": "https://soku.ai/cli",
@@ -19,7 +19,8 @@
19
19
  "ai-agent",
20
20
  "marketing-analytics",
21
21
  "google-ads",
22
- "ga4"
22
+ "ga4",
23
+ "posthog"
23
24
  ],
24
25
  "type": "module",
25
26
  "bin": {
@@ -27,6 +28,7 @@
27
28
  },
28
29
  "files": [
29
30
  "dist",
31
+ "postinstall.cjs",
30
32
  "skills"
31
33
  ],
32
34
  "engines": {
@@ -39,9 +41,10 @@
39
41
  "scripts": {
40
42
  "build": "tsc && mkdir -p dist/generated && cp src/generated/capabilities.json dist/generated/capabilities.json",
41
43
  "typecheck": "tsc --noEmit",
42
- "test": "tsc -p tsconfig.test.json && node --test \".test-build/**/*.test.js\"",
44
+ "test": "tsc -p tsconfig.test.json && find .test-build -name '*.test.js' -print | sort | xargs node --test",
43
45
  "gen:capabilities": "pnpm run build && node scripts/gen-capabilities.ts",
44
46
  "clean": "rm -rf dist .test-build",
47
+ "postinstall": "node postinstall.cjs",
45
48
  "prepublishOnly": "pnpm run clean && pnpm run build"
46
49
  },
47
50
  "dependencies": {
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Refresh previously installed bundled Soku meta-skills after a global npm
4
+ * install. Business skills still update through `soku update skills`, because
5
+ * that path verifies catalog zip checksums and may need network access.
6
+ */
7
+
8
+ const fs = require('node:fs')
9
+ const os = require('node:os')
10
+ const path = require('node:path')
11
+
12
+ const MANIFEST_FILE = '.soku-skills.json'
13
+ const SOKU_META = 'soku'
14
+
15
+ function shouldRun() {
16
+ return process.env.npm_config_global === 'true' || process.env.npm_config_global === '1'
17
+ }
18
+
19
+ function copyDir(source, dest) {
20
+ fs.mkdirSync(dest, { recursive: true })
21
+ for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
22
+ const from = path.join(source, entry.name)
23
+ const to = path.join(dest, entry.name)
24
+ if (entry.isDirectory()) {
25
+ copyDir(from, to)
26
+ } else if (entry.isFile()) {
27
+ fs.copyFileSync(from, to)
28
+ }
29
+ }
30
+ }
31
+
32
+ function loadManifest(baseDir) {
33
+ try {
34
+ return JSON.parse(fs.readFileSync(path.join(baseDir, MANIFEST_FILE), 'utf8'))
35
+ } catch {
36
+ return {}
37
+ }
38
+ }
39
+
40
+ function hasSokuSkill(baseDir) {
41
+ const manifest = loadManifest(baseDir)
42
+ return (
43
+ Object.keys(manifest).length > 0 ||
44
+ fs.existsSync(path.join(baseDir, SOKU_META, 'SKILL.md'))
45
+ )
46
+ }
47
+
48
+ function refreshMetaSkill(baseDir, bundledDir) {
49
+ if (!hasSokuSkill(baseDir)) return
50
+
51
+ const dest = path.join(baseDir, SOKU_META)
52
+ fs.mkdirSync(dest, { recursive: true })
53
+ fs.rmSync(path.join(dest, 'SKILL.md'), { force: true })
54
+ fs.rmSync(path.join(dest, 'references'), { recursive: true, force: true })
55
+ fs.copyFileSync(path.join(bundledDir, 'SKILL.md'), path.join(dest, 'SKILL.md'))
56
+ const referencesDir = path.join(bundledDir, 'references')
57
+ if (fs.existsSync(referencesDir)) {
58
+ copyDir(referencesDir, path.join(dest, 'references'))
59
+ }
60
+
61
+ const manifest = loadManifest(baseDir)
62
+ manifest[SOKU_META] = {
63
+ sha256: '',
64
+ installed_at: new Date().toISOString(),
65
+ source: 'bundled',
66
+ }
67
+ fs.writeFileSync(path.join(baseDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`)
68
+ }
69
+
70
+ function main() {
71
+ if (!shouldRun()) return
72
+ const bundledDir = path.join(__dirname, 'skills', SOKU_META)
73
+ if (!fs.existsSync(path.join(bundledDir, 'SKILL.md'))) return
74
+
75
+ for (const agentDir of ['.claude/skills', '.codex/skills', '.cursor/skills']) {
76
+ try {
77
+ refreshMetaSkill(path.join(os.homedir(), agentDir), bundledDir)
78
+ } catch {
79
+ // Best-effort lifecycle hook: npm install must not fail because an agent
80
+ // skill directory is missing, locked, or otherwise temporarily unreadable.
81
+ }
82
+ }
83
+ }
84
+
85
+ main()
@@ -1,23 +1,26 @@
1
1
  ---
2
2
  name: soku
3
3
  description: >-
4
- Use when calling Soku ads/GA4 marketing-data capabilities from the shell —
4
+ Use when calling Soku ads/GA4/PostHog marketing-data capabilities or SEO
5
+ Hosting domain connections from the shell —
5
6
  running `soku auth login`, switching org/brand with `soku org use` /
6
7
  `soku brand use`, discovering capabilities, calling a data action, routing a
7
8
  third-party API call (Ahrefs/DataForSEO/Firecrawl/Gemini/…) through
8
- `soku egress`, handling 401/403 errors, or installing/updating the Soku CLI.
9
+ `soku egress`, managing `soku seo-hosting connections`, managing
10
+ `soku automation`, using typed Meta Ads write commands, handling 401/403
11
+ errors, or installing/updating the Soku CLI.
9
12
  license: Proprietary
10
13
  metadata:
11
14
  author: nex-ad
12
- version: "0.1"
15
+ version: "0.3"
13
16
  ---
14
17
 
15
18
  # Soku CLI
16
19
 
17
- The `soku` CLI calls Soku's ads + GA4 data capabilities over HTTP. It is the
18
- preferred way for an agent to use Soku data — it works in any shell, with no MCP
19
- host required. Output is JSON on stdout; errors are a JSON envelope on stderr
20
- with a semantic exit code.
20
+ The `soku` CLI calls Soku's ads, GA4, PostHog, SEO Hosting, and Automation
21
+ capabilities over HTTP. It is the preferred way for an agent to use Soku from
22
+ any shell, with no MCP host required. Output is JSON on stdout; errors are a
23
+ JSON envelope on stderr with a semantic exit code.
21
24
 
22
25
  ## Output & exit codes
23
26
 
@@ -36,6 +39,10 @@ Every command prints JSON. When piped (non-TTY) success is
36
39
 
37
40
  `soku` authenticates once per machine with a long-lived, **org-agnostic** session
38
41
  token. The org and brand are chosen at runtime (see Workspace), not at login.
42
+ By default, `soku auth login` requests every resource bundle known to the CLI so
43
+ agents can use reads, approved writes, SEO Hosting, automations, Context Hub,
44
+ asset publishing, and Brand Skills without re-authenticating. Only pass
45
+ `--resource` when the user explicitly wants a narrower token.
39
46
 
40
47
  ### Agent path (recommended): non-blocking split-flow
41
48
 
@@ -89,19 +96,46 @@ soku org list # organizations you belong to
89
96
  soku org use <slug|id> # set active org (id, slug, or name; clears a now-mismatched brand)
90
97
  soku brand list # brands in the active org
91
98
  soku brand use <slug|id> # id, slug, or name
99
+ soku workspace status # show the active org/brand and whether it is ready
100
+ soku workspace resolve <brand> # inspect accessible brand matches across orgs
101
+ soku workspace use-brand <brand> # set org + brand together when the match is unique
92
102
  ```
93
103
 
94
104
  Env overrides for one-off invocations: `SOKU_ORG_ID`, `SOKU_BRAND_ID`.
95
105
 
106
+ Do not infer Soku workspace state from the current shell directory. The working
107
+ directory may contain unrelated README/AGENTS/context files from the user's local
108
+ project. Start with `soku workspace status`; if the target brand is unknown, use
109
+ `soku workspace resolve <brand>` and then `soku workspace use-brand <brand>`.
110
+
111
+ ## Brand memory
112
+
113
+ Memory is scoped to the active Soku workspace, not to the local shell directory.
114
+ After `soku workspace status` is ready, these commands read memory for the
115
+ current brand only:
116
+
117
+ ```bash
118
+ soku memory list
119
+ soku memory search "policy 2026-05-31"
120
+ soku memory get reference noiz-policy-event
121
+ ```
122
+
123
+ Use brand memory as background, hypotheses, and investigation leads. Do not
124
+ treat a memory note as a verified data fact unless this turn also confirms it
125
+ with data actions, change history, billing evidence, or another authoritative
126
+ source. In reports, label memory-derived context separately from data-confirmed
127
+ findings.
128
+
96
129
  ## Calling capabilities (discover with --help, then run)
97
130
 
98
131
  Each data capability is a typed sub-command under its namespace. Discover and
99
132
  inspect them with `--help` — never guess action names or flags.
100
133
 
101
134
  ```bash
102
- soku resources list # what's granted (e.g. data-infra)
103
- soku --help # namespaces: ads, ga4, …
135
+ soku resources list # what's granted (e.g. data-infra, seo-hosting, automation)
136
+ soku --help # namespaces: ads, ga4, posthog,
104
137
  soku ads --help # actions in the ads namespace
138
+ soku posthog --help # customer PostHog read actions
105
139
  soku ads query-single-dimension --help # flags, types, and usage for one action
106
140
 
107
141
  soku ads list-ad-accounts --platform google
@@ -110,8 +144,57 @@ soku ads query-single-dimension --account-id 123 --dimension campaign \
110
144
  ```
111
145
 
112
146
  `<command> --help` is authoritative for valid flags, required vs optional, types,
113
- and usage. Read it before invoking an unfamiliar action. Object/list flags take a
114
- JSON string, e.g. `--filters '{"campaign_id":["123"]}'`.
147
+ and usage. Read it before invoking an unfamiliar action. Typed command names use
148
+ kebab-case (`soku ads query-single-dimension`); raw `soku call` action names use
149
+ registry snake_case (`soku call ads query_single_dimension`). Object/list flags
150
+ take a JSON string, e.g. `--filters '{"campaign_id":["123"]}'`.
151
+
152
+ ### Google Ads GAQL fallback
153
+
154
+ Prefer cached ads analytics first:
155
+
156
+ ```bash
157
+ soku ads list-dimensions --platform google --account-id 123
158
+ soku ads query-single-dimension --account-id 123 --dimension campaign ...
159
+ soku ads query-multi-dimension --account-id 123 --dimensions '["campaign","device"]' ...
160
+ ```
161
+
162
+ Use GAQL only when cached query actions cannot expose the Google-native fields
163
+ or segment combination you need:
164
+
165
+ ```bash
166
+ soku ads get-resource-metadata --platform google --account-id 123 --resource-name campaign
167
+ soku ads gaql-search --platform google --account-id 123 \
168
+ --dimensions '["date","campaign"]' --metrics '["cost","clicks"]' --limit 20
169
+ ```
170
+
171
+ Do not write full `SELECT ... FROM ...` GAQL SQL. `gaql-search` takes structured
172
+ `dimensions`, `metrics`, `filters`, `date_range`, `order_by`, and `limit`; the
173
+ server translates those into real GAQL. For the same action through the raw
174
+ escape hatch, use snake_case: `soku call ads gaql_search --payload '{...}'`.
175
+
176
+ ### PostHog customer analytics
177
+
178
+ PostHog data uses the same backend MCP forwarding gateway as the sandbox agent.
179
+ Start by listing the current brand's granted projects, then inspect the live
180
+ tool menu, then run an allowlisted read tool. Do not ask the user for a project
181
+ id before listing projects.
182
+
183
+ ```bash
184
+ soku posthog list-projects
185
+ soku posthog list-tools --project-id 12345
186
+ soku posthog query --project-id 12345 --tool execute-sql \
187
+ --arguments '{"query":"SELECT count() FROM events WHERE event = '\''$pageview'\''"}'
188
+ ```
189
+
190
+ Use `read-data-schema` before writing HogQL if field names are unclear:
191
+
192
+ ```bash
193
+ soku posthog query --project-id 12345 --tool read-data-schema --arguments '{}'
194
+ ```
195
+
196
+ The CLI default read surface does not expose `posthog/request_change`; writes and
197
+ unvetted PostHog MCP tools remain outside the generated command tree.
115
198
 
116
199
  ### Raw escape hatch
117
200
 
@@ -122,18 +205,284 @@ a newer action than this CLI version ships:
122
205
  ```bash
123
206
  soku call ads list_ad_accounts -p platform=google
124
207
  soku call ads query_single_dimension --payload '{"account_id":"123","dimension":"campaign"}'
208
+ soku call posthog query --payload '{"project_id":"12345","tool":"execute-sql","arguments":{"query":"SELECT count() FROM events"}}'
209
+ ```
210
+
211
+ ### Meta Ads typed commands
212
+
213
+ Meta Ads campaign-tree and creative workflows have an ergonomic typed command
214
+ tree under `soku ads meta`. Use these commands before the raw escape hatch; they
215
+ wrap the same backend actions but expose stable, platform-specific flags and
216
+ client-side validation. Always inspect the exact command help before invoking a
217
+ new verb:
218
+
219
+ ```bash
220
+ soku ads meta --help
221
+ soku ads meta campaign create --help
222
+ soku ads meta asset upload-images --help
223
+ soku ads meta creative create --help
224
+ ```
225
+
226
+ Use the normal agent login before Meta write workflows:
227
+
228
+ ```bash
229
+ soku auth login --no-wait
230
+ soku workspace status
231
+ soku ads list-ad-accounts --platform meta
232
+ ```
233
+
234
+ If you are intentionally using a restricted token, it must include
235
+ `data-infra,ads-write` for this workflow.
236
+
237
+ Useful read helpers:
238
+
239
+ ```bash
240
+ soku ads meta account pages --account-id <meta_account_id>
241
+ soku ads meta campaign get --account-id <meta_account_id> --campaign-id <campaign_id>
242
+ soku ads meta ad get --account-id <meta_account_id> --ad-id <ad_id>
243
+ ```
244
+
245
+ Asset upload is a direct support action: it mutates Meta's asset library but
246
+ does not change delivery and does not require a review summary. Local image
247
+ files are read by the CLI and sent as bytes; public URLs are passed as URLs.
248
+ The result includes `image_hash` values for creative creation.
249
+
250
+ ```bash
251
+ soku ads meta asset upload-images --account-id <meta_account_id> ./hero.png ./square.jpg
252
+ soku ads meta asset upload-images --account-id <meta_account_id> \
253
+ --url https://example.com/hero.png --name-prefix launch
254
+ ```
255
+
256
+ Common single-object write flow. Campaign and ad creation land paused; other
257
+ delivery-changing writes are review-gated. Every gated command requires
258
+ `--summary`, returns a review id, and executes only after human approval.
259
+
260
+ ```bash
261
+ soku ads meta campaign create \
262
+ --account-id <meta_account_id> \
263
+ --name "Launch Test" \
264
+ --objective OUTCOME_TRAFFIC \
265
+ --summary "Create paused Meta traffic campaign Launch Test"
266
+
267
+ soku ads meta adset create \
268
+ --account-id <meta_account_id> \
269
+ --campaign-id <campaign_id> \
270
+ --name "US Prospecting" \
271
+ --optimization-goal LINK_CLICKS \
272
+ --billing-event IMPRESSIONS \
273
+ -p targeting='{"geo_locations":{"countries":["US"]}}' \
274
+ --summary "Create paused Meta ad set US Prospecting"
275
+
276
+ soku ads meta creative create \
277
+ --account-id <meta_account_id> \
278
+ --name "Hero image creative" \
279
+ --page-id <page_id> \
280
+ --image-hash <image_hash> \
281
+ --message "Primary text" \
282
+ --headline "Headline" \
283
+ --link https://example.com \
284
+ --call-to-action-type LEARN_MORE \
285
+ --summary "Create Meta image creative for Launch Test"
286
+
287
+ soku ads meta ad create \
288
+ --account-id <meta_account_id> \
289
+ --adset-id <adset_id> \
290
+ --name "Hero image ad" \
291
+ --creative-id <creative_id> \
292
+ --summary "Create paused Meta ad Hero image ad"
293
+ ```
294
+
295
+ Status controls exist at all three delivery levels:
296
+
297
+ ```bash
298
+ soku ads meta campaign activate --campaign-id <campaign_id> --account-id <meta_account_id> --summary "Activate campaign"
299
+ soku ads meta adset pause --adset-id <adset_id> --account-id <meta_account_id> --summary "Pause ad set"
300
+ soku ads meta ad pause --ad-id <ad_id> --account-id <meta_account_id> --summary "Pause ad"
301
+ ```
302
+
303
+ Bulk commands are intentionally one layer at a time. Each command takes a JSON
304
+ array file, and each item must be an object with a unique non-empty
305
+ `client_ref`. The CLI injects `platform=meta` and the `--account-id`; item fields
306
+ mirror the corresponding raw action payload (`create_campaign`,
307
+ `create_adset`, `create_ad_creative`, or `create_ad`). Bulk approvals execute
308
+ asynchronously after the human approves the review; poll with
309
+ `soku review show <review_id>`.
310
+
311
+ ```bash
312
+ cat > campaigns.json <<'JSON'
313
+ [
314
+ {
315
+ "client_ref": "campaign-us-traffic",
316
+ "name": "US Traffic Test",
317
+ "objective": "OUTCOME_TRAFFIC"
318
+ }
319
+ ]
320
+ JSON
321
+
322
+ soku ads meta campaign bulk-create \
323
+ --account-id <meta_account_id> \
324
+ --items-file campaigns.json \
325
+ --summary "Bulk-create paused Meta campaigns for launch test"
326
+
327
+ soku ads meta adset bulk-create --account-id <meta_account_id> --items-file adsets.json --summary "Bulk-create Meta ad sets"
328
+ soku ads meta creative bulk-create --account-id <meta_account_id> --items-file creatives.json --summary "Bulk-create Meta creatives"
329
+ soku ads meta ad bulk-create --account-id <meta_account_id> --items-file ads.json --summary "Bulk-create paused Meta ads"
125
330
  ```
126
331
 
332
+ Use `soku call ads <raw_action>` only when the Meta action is not exposed in the
333
+ typed tree, such as some audience, pixel, lead form, label, rule, targeting, or
334
+ live-insights operations.
335
+
336
+ ## Automations
337
+
338
+ Use `soku automation` to manage automations for the active brand. Default agent
339
+ login includes the `automation` resource. If you are intentionally using a
340
+ restricted token, include `automation` explicitly:
341
+
342
+ ```bash
343
+ # Restricted-token examples only:
344
+ soku auth login --resource automation
345
+ soku auth login --resource data-infra,automation
346
+ ```
347
+
348
+ The workspace must be ready before any automation call:
349
+
350
+ ```bash
351
+ soku workspace status
352
+ soku org use <slug|id>
353
+ soku brand use <slug|id>
354
+ soku resources list
355
+ ```
356
+
357
+ Commands:
358
+
359
+ ```bash
360
+ soku automation list
361
+ soku automation create --name "Fast check" --prompt "Check account health" --cron "* * * * *" --timezone UTC
362
+ soku automation trigger <automation_id>
363
+ soku automation runs <automation_id>
364
+ ```
365
+
366
+ `create` requires exactly one schedule option:
367
+
368
+ - `--cron <expr>` with optional `--timezone <iana>` (default `UTC`).
369
+ - `--interval-seconds <seconds>` for interval schedules; must be at least
370
+ `3600` and divisible by `60`.
371
+ - `--once-at <iso>` for a one-time UTC instant.
372
+
373
+ `runs` shows run status and a browser link when a conversation exists. The CLI
374
+ does not read conversation content; open the printed link in Studio to inspect
375
+ the actual chat. For local development links, set `SOKU_WEB_BASE`, for example:
376
+
377
+ ```bash
378
+ SOKU_WEB_BASE=http://127.0.0.1:47627 soku automation runs <automation_id>
379
+ ```
380
+
381
+ ## SEO Hosting domain connections
382
+
383
+ Use `soku seo-hosting` to manage SEO Hosting for the active brand. Default agent
384
+ login includes the `seo-hosting` resource. If you are intentionally using a
385
+ restricted token, include `seo-hosting` explicitly:
386
+
387
+ ```bash
388
+ # Restricted-token examples only:
389
+ soku auth login --resource seo-hosting
390
+ soku auth login --resource data-infra,seo-hosting
391
+ ```
392
+
393
+ The workspace must be ready before any connection call:
394
+
395
+ ```bash
396
+ soku workspace status
397
+ soku org use <slug|id>
398
+ soku brand use <slug|id>
399
+ ```
400
+
401
+ ### Posts
402
+
403
+ Use typed `pages` commands for the same SEO Hosting content actions exposed to
404
+ runtime agents through `seo_hosting/*`. SEO Hosting pages are **complete HTML
405
+ documents** (not Markdown) served as owned-media web pages on the brand's
406
+ connected domain; they are identified by `section` + `slug` (there are no post
407
+ ids). They are not social posts.
408
+
409
+ Always check status first:
410
+
411
+ ```bash
412
+ soku seo-hosting status
413
+ ```
414
+
415
+ `status` shows each connected domain, whether it is `live`, and which sections it
416
+ serves. If no domain is live, do not publish yet; connect or fix a domain first.
417
+ When hosting is live:
418
+
419
+ ```bash
420
+ soku seo-hosting pages list --section blog --status draft
421
+ soku seo-hosting pages put --section blog --slug how-to --title "How to ..." --html-file page.html
422
+ soku seo-hosting pages publish --section blog --slug how-to
423
+ soku seo-hosting pages unpublish --section blog --slug how-to
424
+ soku seo-hosting pages delete --section blog --slug how-to --confirm
425
+ soku seo-hosting pages upload-asset --path blog/how-to/hero.png --file ./hero.png
426
+ ```
427
+
428
+ `put` creates or overwrites a page as a **draft** and requires `--section`,
429
+ `--slug`, `--title`, and exactly one HTML source: `--html`, `--html-file`, or
430
+ `--html-stdin`. Optional metadata flags are `--description`, `--template`, and
431
+ `--seo '<json object>'`. Reference images / CSS / fonts by the absolute URL
432
+ returned from `pages upload-asset` (no custom JavaScript — HTML + CSS only).
433
+
434
+ `publish` runs the validation gate (no `<script>` / inline JS) and
435
+ makes the page live once a domain serves its section; it also prints advisory
436
+ `Link warnings` for dead internal links (these do not block publishing — fix the
437
+ links and re-publish). `unpublish` reverts it to draft. Writes run immediately
438
+ (no-review), so confirm intent with the user before publishing or deleting.
439
+
440
+ ### Domain connections
441
+
442
+ Connection commands:
443
+
444
+ ```bash
445
+ soku seo-hosting connections list
446
+ soku seo-hosting connections connect-cname --hostname blog.example.com
447
+ soku seo-hosting connections verify <connection_id>
448
+ soku seo-hosting connections disconnect <connection_id> --confirm
449
+ ```
450
+
451
+ Cloudflare Worker reverse proxy setup runs a probe before provisioning. Use it
452
+ when the customer's root or existing hostname is already on Cloudflare and SEO
453
+ Hosting needs to mount sections such as `/blog`, `/use-cases`, or
454
+ `/alternatives`:
455
+
456
+ ```bash
457
+ soku seo-hosting connections probe --hostname example.com --sections blog,use-cases
458
+ soku seo-hosting connections connect-worker --hostname example.com \
459
+ --sections blog,use-cases --cf-token-env CLOUDFLARE_API_TOKEN
460
+ printf %s "$CLOUDFLARE_API_TOKEN" | soku seo-hosting connections connect-worker \
461
+ --hostname example.com --sections blog --cf-token-stdin
462
+ ```
463
+
464
+ Allowed sections are `blog`, `use-cases`, and `alternatives`; omitted sections
465
+ default to `blog`. If the probe reports existing mounted content, add
466
+ `--accept-conflicts` only after the user confirms SEO Hosting may shadow those
467
+ paths. If the hostname serves its own Next.js assets, add
468
+ `--accept-next-assets-warning` only after confirming `/_next/static/*` may route
469
+ through the Worker.
470
+
471
+ Do not pass Cloudflare API tokens as literal argv values or print them. Use
472
+ `--cf-token-env <ENV_NAME>` or `--cf-token-stdin` exactly once. Vercel OAuth
473
+ domain connections are not exposed in the CLI yet; use the Studio web settings
474
+ for Vercel-backed domains.
475
+
127
476
  ## Write actions (human approval required)
128
477
 
129
- Some actions mutate state (e.g. conversion-group writes) and are **review-gated**.
130
- They are NOT in the typed command tree call them with `soku call` and a
131
- required `--summary` describing the change. The call does NOT execute; it returns
132
- a pending review that a human approves.
478
+ Some actions mutate state and are **review-gated**. Prefer typed commands when
479
+ they exist (`soku ads meta ...`, `soku ads google ...`). Use `soku call` for raw
480
+ actions that are not exposed as typed commands. Review-gated commands do NOT
481
+ execute immediately; they return a review that a human approves.
133
482
 
134
483
  ```bash
135
484
  soku call ads create_conversion_group --summary "create group: qualified leads" -p name="Qualified leads"
136
- # → {"ok":true,"data":{"status":"pending_review","pending_review_id":"<id>","summary":"..."}}
485
+ # → {"ok":true,"data":{"status":"pending_review","review_id":"<id>","summary":"..."}}
137
486
 
138
487
  soku review list # see pending reviews
139
488
  soku review show <id> # full payload + (after approval) result
@@ -141,15 +490,16 @@ soku review approve <id> # a human approves → the action executes now
141
490
  soku review deny <id> --feedback "wrong account"
142
491
  ```
143
492
 
144
- - The write resource must be granted to the session (request it at login:
145
- `soku auth login --resource data-infra,conversion-groups-write`). Without it,
146
- `soku call` returns 403.
493
+ - The write resource must be granted to the session. Default login includes
494
+ `conversion-groups-write`; restricted tokens must include it explicitly, for
495
+ example `soku auth login --resource data-infra,conversion-groups-write`.
496
+ Without it, `soku call` returns 403.
147
497
  - Approval is **single-use**: one review = one execution. Approving an already
148
498
  decided review is a no-op that returns the existing result.
149
499
  - Do NOT loop `soku review approve` expecting retries; a failed approval is
150
500
  terminal — create a fresh `soku call` to try again.
151
- - As an agent: surface the `pending_review_id` + summary to the user and let
152
- THEM run `soku review approve`. Do not approve on the user's behalf.
501
+ - As an agent: surface the `review_id` + summary to the user and let THEM run
502
+ `soku review approve`. Do not approve on the user's behalf.
153
503
 
154
504
  ## Third-party APIs (egress)
155
505
 
@@ -180,18 +530,25 @@ returned verbatim on stdout.
180
530
  ## Installing / updating the skill
181
531
 
182
532
  ```bash
183
- soku update-check # check whether the CLI itself needs updating
533
+ soku update status # show CLI + installed skill update status
534
+ soku update skills # refresh this meta-skill and installed business skills
535
+ soku update cli # install the latest CLI from npm
184
536
  soku skill install # this meta-skill into .claude / .codex / .cursor (project)
185
537
  soku skill install --global # into ~/.claude, ~/.codex, ~/.cursor
186
538
  soku skill install --agent claude --global
187
539
  ```
188
540
 
189
- If `soku update-check` reports an available CLI update, ask the user before
190
- running:
541
+ Normal `soku` commands schedule a background refresh for installed Soku-managed
542
+ skills at most once every 24 hours. Set `SOKU_NO_SKILL_AUTO_UPDATE=1` to disable
543
+ skill auto updates, or `SOKU_UPDATE_INTERVAL_HOURS=<n>` to change the interval.
191
544
 
192
- ```bash
193
- npm i -g @soku-ai/cli
194
- ```
545
+ The CLI binary itself is advisory by default. To opt into automatic npm CLI
546
+ updates, set `SOKU_AUTO_UPDATE_CLI=1`; otherwise run `soku update cli` when
547
+ `soku update status` shows a newer version.
548
+
549
+ A global `npm i -g @soku-ai/cli` refreshes this already-installed global
550
+ meta-skill after npm finishes installing. It does not install new skills and it
551
+ does not update project-local skills.
195
552
 
196
553
  ## Installing business skills (account audits, Google Ads, reports, …)
197
554
 
@@ -205,10 +562,21 @@ soku skill list # browse the catalog
205
562
  soku skill install account-audit # install one (into soku-account-audit/)
206
563
  soku skill install ads-report google-ads
207
564
  soku skill install --all # install everything
208
- soku skill installed # what's installed locally
565
+ soku skill status # what's installed locally
566
+ soku skill list-installed # same as status
209
567
  soku skill remove account-audit
210
568
  ```
211
569
 
570
+ Catalog slugs are used for install/remove commands (`ads-report`). Installed AI
571
+ client skill names are Soku-prefixed (`soku-ads-report`). When asking an agent to
572
+ run an installed business skill, write `use @soku-ads-report skill`, not
573
+ `@ads-report`.
574
+
575
+ Business skill updates are detected from `.soku-skills.json`: for each installed
576
+ catalog slug, Soku compares local `version`, `sha256`, and `source` against the
577
+ latest catalog `index.json`. A changed version, changed hash, or old source is
578
+ treated as an update; missing catalog entries are reported but not deleted.
579
+
212
580
  Each installed skill carries a "Running this skill with the Soku CLI" section
213
581
  (its data actions mapped to `soku call`, its third-party APIs to `soku egress`),
214
582
  so it runs through this same CLI — no in-sandbox tools, no local API keys.
@@ -217,9 +585,11 @@ so it runs through this same CLI — no in-sandbox tools, no local API keys.
217
585
 
218
586
  - Never print the access token (it grants account access). Prefer `SOKU_TOKEN`
219
587
  for CI rather than echoing it.
220
- - Reads (`data-infra`) run directly. Writes are review-gated: `soku call`
221
- creates a pending review and only `soku review approve` (a human action)
222
- executes it. Don't approve on the user's behalf, and don't assume a
223
- `--yes`-style bypass exists there isn't one.
588
+ - Reads (`data-infra`) run directly. Review-gated writes, whether invoked with
589
+ typed commands or `soku call`, create a pending review and only
590
+ `soku review approve` (a human action) executes them. Direct support writes
591
+ such as Meta image upload or SEO Hosting page writes execute immediately, so
592
+ confirm user intent before running them. Don't approve on the user's behalf,
593
+ and don't assume a `--yes`-style bypass exists — there isn't one.
224
594
  - Pass user-provided values as separate argv elements (the `-p key=value` form),
225
595
  never by string-concatenating them into a shell command.