@oxygen-agent/cli 1.1014.136 → 1.1014.619
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/admin-primary-providers-render.js +27 -19
- package/dist/cli-output.d.ts +12 -0
- package/dist/cli-output.js +395 -0
- package/dist/cli-paths.d.ts +1 -0
- package/dist/cli-paths.js +3 -0
- package/dist/cli-profile-context.d.ts +3 -0
- package/dist/cli-profile-context.js +13 -0
- package/dist/command-manifest.d.ts +15 -0
- package/dist/command-manifest.js +23 -1
- package/dist/command-support.d.ts +438 -0
- package/dist/command-support.js +1610 -0
- package/dist/commands/action-column.d.ts +3 -0
- package/dist/commands/action-column.js +43 -0
- package/dist/commands/activation.d.ts +3 -0
- package/dist/commands/activation.js +24 -0
- package/dist/commands/admin.d.ts +3 -0
- package/dist/commands/admin.js +347 -0
- package/dist/commands/ads.d.ts +3 -0
- package/dist/commands/ads.js +62 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.js +274 -0
- package/dist/commands/api-keys.d.ts +3 -0
- package/dist/commands/api-keys.js +77 -0
- package/dist/commands/approvals.d.ts +3 -0
- package/dist/commands/approvals.js +518 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.js +230 -0
- package/dist/commands/billing.d.ts +3 -0
- package/dist/commands/billing.js +357 -0
- package/dist/commands/blueprints.d.ts +3 -0
- package/dist/commands/blueprints.js +312 -0
- package/dist/commands/budget.d.ts +3 -0
- package/dist/commands/budget.js +55 -0
- package/dist/commands/callables.d.ts +3 -0
- package/dist/commands/callables.js +111 -0
- package/dist/commands/capabilities.d.ts +3 -0
- package/dist/commands/capabilities.js +44 -0
- package/dist/commands/cells.d.ts +3 -0
- package/dist/commands/cells.js +94 -0
- package/dist/commands/columns.d.ts +3 -0
- package/dist/commands/columns.js +1628 -0
- package/dist/commands/commands.d.ts +3 -0
- package/dist/commands/commands.js +45 -0
- package/dist/commands/comments.d.ts +3 -0
- package/dist/commands/comments.js +188 -0
- package/dist/commands/companies.d.ts +3 -0
- package/dist/commands/companies.js +312 -0
- package/dist/commands/connections.d.ts +3 -0
- package/dist/commands/connections.js +39 -0
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.js +170 -0
- package/dist/commands/copilot.d.ts +3 -0
- package/dist/commands/copilot.js +565 -0
- package/dist/commands/crm.d.ts +3 -0
- package/dist/commands/crm.js +1672 -0
- package/dist/commands/custom-integrations.d.ts +3 -0
- package/dist/commands/custom-integrations.js +140 -0
- package/dist/commands/dashboards.d.ts +3 -0
- package/dist/commands/dashboards.js +240 -0
- package/dist/commands/db.d.ts +3 -0
- package/dist/commands/db.js +190 -0
- package/dist/commands/deliverability.d.ts +3 -0
- package/dist/commands/deliverability.js +90 -0
- package/dist/commands/directory.d.ts +3 -0
- package/dist/commands/directory.js +101 -0
- package/dist/commands/domains.d.ts +3 -0
- package/dist/commands/domains.js +604 -0
- package/dist/commands/egress.d.ts +3 -0
- package/dist/commands/egress.js +130 -0
- package/dist/commands/email.d.ts +3 -0
- package/dist/commands/email.js +49 -0
- package/dist/commands/engagement.d.ts +3 -0
- package/dist/commands/engagement.js +189 -0
- package/dist/commands/enrich-column.d.ts +3 -0
- package/dist/commands/enrich-column.js +149 -0
- package/dist/commands/enrichment.d.ts +3 -0
- package/dist/commands/enrichment.js +49 -0
- package/dist/commands/feedback.d.ts +3 -0
- package/dist/commands/feedback.js +96 -0
- package/dist/commands/feeds.d.ts +3 -0
- package/dist/commands/feeds.js +486 -0
- package/dist/commands/find.d.ts +3 -0
- package/dist/commands/find.js +159 -0
- package/dist/commands/followers.d.ts +3 -0
- package/dist/commands/followers.js +39 -0
- package/dist/commands/formulas.d.ts +3 -0
- package/dist/commands/formulas.js +43 -0
- package/dist/commands/home.d.ts +3 -0
- package/dist/commands/home.js +22 -0
- package/dist/commands/inbox.d.ts +3 -0
- package/dist/commands/inbox.js +779 -0
- package/dist/commands/integrations.d.ts +3 -0
- package/dist/commands/integrations.js +290 -0
- package/dist/commands/knowledge.d.ts +3 -0
- package/dist/commands/knowledge.js +874 -0
- package/dist/commands/lead-sourcing.d.ts +3 -0
- package/dist/commands/lead-sourcing.js +52 -0
- package/dist/commands/limits.d.ts +3 -0
- package/dist/commands/limits.js +54 -0
- package/dist/commands/linkedin.d.ts +3 -0
- package/dist/commands/linkedin.js +275 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.js +347 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.js +12 -0
- package/dist/commands/mailboxes.d.ts +3 -0
- package/dist/commands/mailboxes.js +1290 -0
- package/dist/commands/managed-inboxes.d.ts +3 -0
- package/dist/commands/managed-inboxes.js +361 -0
- package/dist/commands/messages.d.ts +3 -0
- package/dist/commands/messages.js +78 -0
- package/dist/commands/notetaker.d.ts +3 -0
- package/dist/commands/notetaker.js +79 -0
- package/dist/commands/observability.d.ts +3 -0
- package/dist/commands/observability.js +125 -0
- package/dist/commands/orgs.d.ts +3 -0
- package/dist/commands/orgs.js +237 -0
- package/dist/commands/people.d.ts +3 -0
- package/dist/commands/people.js +353 -0
- package/dist/commands/posts.d.ts +3 -0
- package/dist/commands/posts.js +139 -0
- package/dist/commands/profiles.d.ts +3 -0
- package/dist/commands/profiles.js +250 -0
- package/dist/commands/projects.d.ts +3 -0
- package/dist/commands/projects.js +94 -0
- package/dist/commands/prompts.d.ts +3 -0
- package/dist/commands/prompts.js +84 -0
- package/dist/commands/publishing.d.ts +3 -0
- package/dist/commands/publishing.js +1419 -0
- package/dist/commands/recipes.d.ts +3 -0
- package/dist/commands/recipes.js +120 -0
- package/dist/commands/reviews.d.ts +3 -0
- package/dist/commands/reviews.js +75 -0
- package/dist/commands/rows.d.ts +3 -0
- package/dist/commands/rows.js +27 -0
- package/dist/commands/runs.d.ts +3 -0
- package/dist/commands/runs.js +37 -0
- package/dist/commands/search.d.ts +3 -0
- package/dist/commands/search.js +182 -0
- package/dist/commands/senders.d.ts +3 -0
- package/dist/commands/senders.js +393 -0
- package/dist/commands/sequences.d.ts +3 -0
- package/dist/commands/sequences.js +2322 -0
- package/dist/commands/session.d.ts +3 -0
- package/dist/commands/session.js +109 -0
- package/dist/commands/signals.d.ts +3 -0
- package/dist/commands/signals.js +289 -0
- package/dist/commands/signup-leads.d.ts +3 -0
- package/dist/commands/signup-leads.js +46 -0
- package/dist/commands/skills.d.ts +3 -0
- package/dist/commands/skills.js +50 -0
- package/dist/commands/sourcing.d.ts +3 -0
- package/dist/commands/sourcing.js +27 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.js +51 -0
- package/dist/commands/supabase.d.ts +3 -0
- package/dist/commands/supabase.js +128 -0
- package/dist/commands/support.d.ts +3 -0
- package/dist/commands/support.js +651 -0
- package/dist/commands/suppressions.d.ts +3 -0
- package/dist/commands/suppressions.js +577 -0
- package/dist/commands/table-ingestions.d.ts +3 -0
- package/dist/commands/table-ingestions.js +145 -0
- package/dist/commands/table-runs.d.ts +3 -0
- package/dist/commands/table-runs.js +277 -0
- package/dist/commands/tables.d.ts +3 -0
- package/dist/commands/tables.js +3505 -0
- package/dist/commands/tags.d.ts +3 -0
- package/dist/commands/tags.js +147 -0
- package/dist/commands/tools.d.ts +3 -0
- package/dist/commands/tools.js +246 -0
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.js +106 -0
- package/dist/commands/verify.d.ts +3 -0
- package/dist/commands/verify.js +27 -0
- package/dist/commands/viewers.d.ts +3 -0
- package/dist/commands/viewers.js +72 -0
- package/dist/commands/voice.d.ts +3 -0
- package/dist/commands/voice.js +351 -0
- package/dist/commands/whatsapp.d.ts +3 -0
- package/dist/commands/whatsapp.js +226 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.js +110 -0
- package/dist/commands/worker.d.ts +3 -0
- package/dist/commands/worker.js +60 -0
- package/dist/commands/workflows.d.ts +3 -0
- package/dist/commands/workflows.js +1752 -0
- package/dist/commands/workspace.d.ts +3 -0
- package/dist/commands/workspace.js +94 -0
- package/dist/compact-list-flags.d.ts +7 -0
- package/dist/compact-list-flags.js +12 -0
- package/dist/crm-support-flags.js +14 -5
- package/dist/customer-operation-commands.js +8 -6
- package/dist/diagnostic-uploader.d.ts +3 -1
- package/dist/diagnostic-uploader.js +29 -8
- package/dist/help.d.ts +5 -0
- package/dist/help.js +22 -0
- package/dist/index.d.ts +98 -1
- package/dist/index.js +331 -30719
- package/dist/local-module-loader.d.ts +4 -0
- package/dist/local-module-loader.js +156 -0
- package/dist/visual-commands.js +9 -5
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +2 -2
- package/node_modules/@oxygen/shared/dist/agent-session-schedule.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/agent-session-schedule.js +69 -0
- package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +14 -5
- package/node_modules/@oxygen/shared/dist/agent-sessions.js +14 -3
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +175 -14
- package/node_modules/@oxygen/shared/dist/compact-list-result.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/compact-list-result.js +27 -0
- package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +5 -2
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +4 -2
- package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.d.ts +57 -0
- package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.js +23 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +43 -2
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +20 -16
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.d.ts +49 -0
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.js +14 -0
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-sync.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +60 -7
- package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +9 -1
- package/node_modules/@oxygen/shared/dist/contracts/operations.js +13 -5
- package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/teammates.d.ts +16 -0
- package/node_modules/@oxygen/shared/dist/contracts/teammates.js +115 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +21 -3
- package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +31 -1
- package/node_modules/@oxygen/shared/dist/cost-estimate.js +56 -15
- package/node_modules/@oxygen/shared/dist/diagnostic-credentials.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-credentials.js +2 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-lock.js +71 -44
- package/node_modules/@oxygen/shared/dist/diagnostic-spool.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-spool.js +348 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.js +42 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-storage.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/diagnostic-storage.js +59 -213
- package/node_modules/@oxygen/shared/dist/diagnostics.js +3 -2
- package/node_modules/@oxygen/shared/dist/email-dsn.d.ts +16 -5
- package/node_modules/@oxygen/shared/dist/email-dsn.js +97 -1
- package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.js +5 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +4 -5
- package/node_modules/@oxygen/shared/dist/file-import.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/file-import.js +21 -2
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +30 -0
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +20 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/knowledge-seed-content.d.ts +17 -7
- package/node_modules/@oxygen/shared/dist/knowledge-seed-content.js +81 -17
- package/node_modules/@oxygen/shared/dist/langfuse.js +8 -4
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +6 -3
- package/node_modules/@oxygen/shared/dist/mailbox-import.js +9 -9
- package/node_modules/@oxygen/shared/dist/mcp-server-info.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/mcp-server-info.js +10 -0
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +14 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.js +17 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
- package/node_modules/@oxygen/shared/dist/provider-selection.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/provider-selection.js +3 -0
- package/node_modules/@oxygen/shared/dist/repricing.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/repricing.js +2 -2
- package/node_modules/@oxygen/shared/dist/schedule-label.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/schedule-label.js +12 -0
- package/node_modules/@oxygen/shared/dist/sequence-failures.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +20 -6
- package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +55 -0
- package/node_modules/@oxygen/shared/dist/teammate-groups.js +74 -0
- package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/teammate-messages.js +88 -0
- package/node_modules/@oxygen/shared/dist/teammate-notes.d.ts +58 -0
- package/node_modules/@oxygen/shared/dist/teammate-notes.js +73 -0
- package/node_modules/@oxygen/shared/dist/teammate-routines.d.ts +95 -0
- package/node_modules/@oxygen/shared/dist/teammate-routines.js +266 -0
- package/node_modules/@oxygen/shared/dist/teammate-tasks.d.ts +56 -0
- package/node_modules/@oxygen/shared/dist/teammate-tasks.js +114 -0
- package/node_modules/@oxygen/shared/dist/teammates.d.ts +78 -0
- package/node_modules/@oxygen/shared/dist/teammates.js +140 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +120 -0
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/visual-render.d.ts +9 -1
- package/node_modules/@oxygen/shared/dist/visual-render.js +12 -5
- package/node_modules/@oxygen/shared/dist/warmup-metrics.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/warmup-metrics.js +44 -0
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { requestOxygen } from "../http-client.js";
|
|
2
|
+
import { readOption } from "../util.js";
|
|
3
|
+
import { MANAGED_DATA_SUPPLIERS } from "@oxygen/shared/data-suppliers";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
export function registerViewersFamily(program, handleAsyncAction) {
|
|
6
|
+
program.addCommand(new Command("viewers")
|
|
7
|
+
.description("Read the named LinkedIn 'who viewed my profile' (WVMP) viewers into a Clay-like workspace table that leads with each person's photo, name and `viewed_account` (\"Viewed profile of\": every one of your connected accounts that person viewed). `import` arms (or resumes) a slow, durable background drip per account under a dedicated conservative read budget; `pause` switches an account off without touching rows or the table; `enrichment` switches standing paid full-profile enrichment of new viewers on/off. LinkedIn hides anonymous/private viewers, so the table is always a partial sample. `status` reports two counts with two denominators: `viewers_count` is named viewers captured from LinkedIn into the per-account mirror (a person who viewed two of your accounts counts twice) and `table_row_count` is rows in the table, one per viewer — they are not the same number and neither is a page or read counter.")
|
|
8
|
+
.addCommand(new Command("import")
|
|
9
|
+
.description("Start (or re-arm, or resume a paused account) a durable profile-viewers (WVMP) import. Named viewers drip into a workspace table (one row per viewer, upserted on provider_id) from the deduped per-account mirror over many ticks; poll `viewers status` to watch them accrue. Two columns are coarser than they look: `view_count` counts the capture walks a viewer re-appeared in (about one every 6 hours), not profile views, and `last_viewed_at` is approximate — LinkedIn only reports 'Viewed 3w ago'-style recency, stored as the earliest day it can mean. Do not run this for an account whose viewers already land through a LinkedIn capture feed (`viewers status` says `landing_via: feed`): it would arm a second writer into a different table. The import itself sends no messages and charges no Oxygen credits; `--enrich`/`--no-enrich` switch standing per-viewer paid enrichment on or off in the same call (omit to leave it as-is) — see `viewers enrichment`.")
|
|
10
|
+
.option("--account <ref>", "Sender account to import (sender id, connection id, or Unipile account id); also switches a paused account back on. Omit to import every active LinkedIn account.")
|
|
11
|
+
.option("--table <ref>", "Existing workspace table (id or slug) to import into. Omit to create or reuse a 'LinkedIn Profile Viewers' table.")
|
|
12
|
+
.option("--enrich", "Also switch on standing full-profile enrichment for new viewers (see `viewers enrichment`). Omit to leave enrichment as-is.")
|
|
13
|
+
.option("--no-enrich", "Also switch off standing full-profile enrichment. Omit to leave enrichment as-is.")
|
|
14
|
+
.option("--json", "Print a JSON envelope.")
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
await handleAsyncAction("viewers import", options, () => {
|
|
17
|
+
const account = readOption(options.account);
|
|
18
|
+
const table = readOption(options.table);
|
|
19
|
+
return requestOxygen("/api/cli/linkedin/viewers/import", {
|
|
20
|
+
method: "POST",
|
|
21
|
+
body: {
|
|
22
|
+
...(account ? { account } : {}),
|
|
23
|
+
...(table ? { table } : {}),
|
|
24
|
+
...(options.enrich === undefined ? {} : { enrich: options.enrich }),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}))
|
|
29
|
+
.addCommand(new Command("status")
|
|
30
|
+
.description("Show durable profile-viewers (WVMP) import progress: `accounts[]` lists every connected LinkedIn account with its display_name, headline, picture_url, per-account `on` (true when switched on, whatever its health) and state (off|collecting|waiting|paused|attention) with state_note, last_read_at/next_read_at, and whether it is toggleable — plus a preview of the viewers table, `enrichment` (on, credits_per_person, columns — see `viewers enrichment`) and a deep-link to open it. Read job/org `health` before `last_error`, worst state wins: `landing` — every import lands somewhere (its own table, or the LinkedIn capture feed named in `landing_via`/`jobs[].landing`) and is due; `waiting` — it lands, but the last tick parked on the account's daily read quota until `next_tick_at` (a wait, not a fault); `paused` — an account was switched off with `viewers pause`; `viewers import --account <ref>` switches it back on; `stalled` — the landing feed is stopped or paused, or the import spent its retries; `health_note` carries the exact repair (`oxygen feeds resume <id>` or `viewers import`); `unbound` — armed with no table and no feed, reading LinkedIn every tick and writing nothing, without ever recording an error (repair: `viewers import`); `none` — nothing armed. Two counts, two denominators: `viewers_count` is named viewers captured from LinkedIn into the per-account mirror (`viewers_count_scope` says workspace or one account; a person who viewed two of your accounts counts twice), `table_row_count` is rows in the table (one per viewer). They diverge while landing lags capture.")
|
|
31
|
+
.option("--account <ref>", "Scope to one sender account (sender id, connection id, or Unipile account id).")
|
|
32
|
+
.option("--json", "Print a JSON envelope.")
|
|
33
|
+
.action(async (options) => {
|
|
34
|
+
await handleAsyncAction("viewers status", options, () => {
|
|
35
|
+
const account = readOption(options.account);
|
|
36
|
+
const params = new URLSearchParams();
|
|
37
|
+
if (account)
|
|
38
|
+
params.set("account", account);
|
|
39
|
+
const suffix = params.toString();
|
|
40
|
+
return requestOxygen(`/api/cli/linkedin/viewers/status${suffix ? `?${suffix}` : ""}`);
|
|
41
|
+
});
|
|
42
|
+
}))
|
|
43
|
+
.addCommand(new Command("pause")
|
|
44
|
+
.description("Switch the profile-viewers import off for one account or all: rows and the table stay exactly as they are, and `viewers import --account <ref>` switches it back on. Imports landed by a LinkedIn capture feed are not touched here — pause the feed instead with `oxygen feeds pause <id>`. Free.")
|
|
45
|
+
.option("--account <ref>", "Sender account to pause (sender id, connection id, or Unipile account id). Omit to pause every account.")
|
|
46
|
+
.option("--json", "Print a JSON envelope.")
|
|
47
|
+
.action(async (options) => {
|
|
48
|
+
await handleAsyncAction("viewers pause", options, () => {
|
|
49
|
+
const account = readOption(options.account);
|
|
50
|
+
return requestOxygen("/api/cli/linkedin/viewers/pause", {
|
|
51
|
+
method: "POST",
|
|
52
|
+
body: { ...(account ? { account } : {}) },
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}))
|
|
56
|
+
.addCommand(new Command("enrichment")
|
|
57
|
+
.description("Turn automatic full-profile enrichment of NEW profile viewers on or off for the viewers table (" + MANAGED_DATA_SUPPLIERS.scraper + " profile: about, current role and company, experience, education, location). This is STANDING credit spend, charged credits_per_person credits per enriched viewer, applied only to viewers first seen after `enrichment.enabled_at`, each delivery bounded by `enrichment.max_credits_per_delivery` (the plan default). Switching on adds the table's Person profile columns (a paid lookup plus its extractors); they stay, with their data, when switched off. Existing rows are never charged by it — enrich them on demand by running the Person profile column. `viewers status` shows `enrichment` (on, credits_per_person, enabled_at, max_credits_per_delivery, next_capture_at: when new viewers can next arrive); the runs appear on the table's Runs page. Requires exactly one of --on or --off.")
|
|
58
|
+
.option("--on", "Switch standing enrichment on for new viewers.")
|
|
59
|
+
.option("--off", "Switch standing enrichment off.")
|
|
60
|
+
.option("--json", "Print a JSON envelope.")
|
|
61
|
+
.action(async (options) => {
|
|
62
|
+
await handleAsyncAction("viewers enrichment", options, () => {
|
|
63
|
+
if (options.on === options.off) {
|
|
64
|
+
throw new Error("Pass exactly one of --on or --off.");
|
|
65
|
+
}
|
|
66
|
+
return requestOxygen("/api/cli/linkedin/viewers/enrichment", {
|
|
67
|
+
method: "POST",
|
|
68
|
+
body: { enabled: options.on === true },
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
})));
|
|
72
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { splitCommaList } from "../command-support.js";
|
|
2
|
+
import { requestOxygen } from "../http-client.js";
|
|
3
|
+
import { readOption } from "../util.js";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
/** The shared search/buy filters, in the snake_case the voice numbers route reads. */
|
|
6
|
+
function voiceNumberFilters(options) {
|
|
7
|
+
const areaCode = readOption(options.areaCode);
|
|
8
|
+
const region = readOption(options.region);
|
|
9
|
+
const country = readOption(options.country);
|
|
10
|
+
return {
|
|
11
|
+
...(areaCode ? { area_code: areaCode } : {}),
|
|
12
|
+
...(region ? { in_region: region } : {}),
|
|
13
|
+
...(country ? { iso_country: country.toUpperCase() } : {}),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function registerVoiceFamily(program, handleAsyncAction) {
|
|
17
|
+
program.addCommand(new Command("voice")
|
|
18
|
+
.description("The call channel. `tasks` works the call queue — leads waiting for a HUMAN to dial, queued by sequence call steps, CRM records, table rows, and replies. Claiming takes a lease so two reps never dial the same prospect. `numbers` manages the dialing pool: list, search, buy, tag, cap, release. Nothing here places a call: a call is live audio a rep holds in the web softphone at /sequencer/calls (a beta feature: Settings → Beta features; 5 credits per connected minute), so the terminal works everything around it. Only `numbers buy --approved` starts a charge.")
|
|
19
|
+
.addCommand(new Command("tasks")
|
|
20
|
+
.description("Work the call queue: queue | list | claim | complete | extend | skip | override.\n\nA call that nobody answers is dispositioned for you from the call itself, which\nalso resumes any sequence enrollment parked on it — so `complete` and `skip`\nreport already_recorded:true rather than failing when that has happened. You log\nonly what a person said.\n\nWhat blocks a dial, and what you can do about it:\n calling_window lead's local time outside 09:00-20:00 waivable per lead\n unknown_timezone no timezone on the lead, so the window\n cannot be checked (fails closed) waivable per lead\n daily_cap the chosen number hit today's dial cap waivable per lead\n suppressed on the workspace do-not-call list NEVER waivable\n destination_blocked your numbers cannot reach that country NEVER waivable\n\n`list` reports dialable, block_reason and block_detail per lead, plus\ndialable_count and blocked_by_reason for the page. Waive one with `override`.")
|
|
21
|
+
.addCommand(new Command("queue")
|
|
22
|
+
.description("Put a lead INTO the call queue. Idempotent: a lead who already has an open task comes back with created:false rather than a duplicate, because the queue guarantees one open call per person. Queuing is not dialing — it writes a row a human later acts on. Consumes 0 credits.")
|
|
23
|
+
.requiredOption("--phone <e164>", "The lead's number in E.164 (a leading + then 2-15 digits).")
|
|
24
|
+
.option("--name <name>", "The lead's name, shown on the dial screen.")
|
|
25
|
+
.option("--timezone <tz>", "IANA timezone (e.g. America/New_York). Calls are allowed 09:00-20:00 in the lead's local time; WITHOUT a timezone that window cannot be checked, so the guardrail fails closed and the lead cannot be dialed.")
|
|
26
|
+
.option("--override <guardrails>", "Comma-separated guardrails to waive for THIS lead: calling_window (the 09:00-20:00 local window), unknown_timezone, daily_cap. Do-not-call, credits, number health and destination coverage are never waivable.")
|
|
27
|
+
.option("--note <text>", "Talking points for the rep. Shown on the dial screen, never sent to the lead.")
|
|
28
|
+
.option("--assigned-user-id <id>", "Assign the call task to this workspace user.")
|
|
29
|
+
.option("--source <source>", "Origin: manual (default), crm, table, reply, sequence.")
|
|
30
|
+
.option("--priority <n>", "Higher sorts first in the queue.")
|
|
31
|
+
.option("--due <iso>", "ISO-8601 timestamp; the task stays out of the queue until then.")
|
|
32
|
+
.option("--crm-record <id>", "Bind the task to a CRM record.")
|
|
33
|
+
.option("--subject-provider <name>", "Bind the task to a subject in a CRM you already use (e.g. hubspot, attio) instead of an Oxygen record. Requires --subject-external-id.")
|
|
34
|
+
.option("--subject-external-id <id>", "That provider's own id for the person. Requires --subject-provider.")
|
|
35
|
+
.option("--table <id>", "Bind the task to a table.")
|
|
36
|
+
.option("--row <id>", "Bind the task to a table row.")
|
|
37
|
+
.option("--json", "Print a JSON envelope.")
|
|
38
|
+
.action(async (options) => {
|
|
39
|
+
await handleAsyncAction("voice tasks queue", options, () => {
|
|
40
|
+
const phone = readOption(options.phone);
|
|
41
|
+
if (!phone)
|
|
42
|
+
throw new Error("--phone is required.");
|
|
43
|
+
const name = readOption(options.name);
|
|
44
|
+
const timezone = readOption(options.timezone);
|
|
45
|
+
const note = readOption(options.note);
|
|
46
|
+
const source = readOption(options.source);
|
|
47
|
+
const priority = readOption(options.priority);
|
|
48
|
+
const due = readOption(options.due);
|
|
49
|
+
const crmRecord = readOption(options.crmRecord);
|
|
50
|
+
const subjectProvider = readOption(options.subjectProvider);
|
|
51
|
+
const subjectExternalId = readOption(options.subjectExternalId);
|
|
52
|
+
// Caught server-side too (and by a CHECK), but failing here
|
|
53
|
+
// costs no round trip and names the missing flag.
|
|
54
|
+
if (Boolean(subjectProvider) !== Boolean(subjectExternalId)) {
|
|
55
|
+
throw new Error("--subject-provider and --subject-external-id must be passed together.");
|
|
56
|
+
}
|
|
57
|
+
const table = readOption(options.table);
|
|
58
|
+
const row = readOption(options.row);
|
|
59
|
+
const override = readOption(options.override);
|
|
60
|
+
return requestOxygen("/api/cli/voice/tasks", {
|
|
61
|
+
method: "POST",
|
|
62
|
+
body: {
|
|
63
|
+
action: "queue",
|
|
64
|
+
...(readOption(options.assignedUserId) ? { assigned_user_id: readOption(options.assignedUserId) } : {}),
|
|
65
|
+
lead_phone: phone,
|
|
66
|
+
...(name ? { lead_name: name } : {}),
|
|
67
|
+
...(timezone ? { lead_timezone: timezone } : {}),
|
|
68
|
+
...(note ? { note } : {}),
|
|
69
|
+
...(source ? { source } : {}),
|
|
70
|
+
...(priority ? { priority: Number(priority) } : {}),
|
|
71
|
+
...(due ? { due_at: due } : {}),
|
|
72
|
+
...(crmRecord ? { crm_record_id: crmRecord } : {}),
|
|
73
|
+
...(subjectProvider ? { subject_provider: subjectProvider } : {}),
|
|
74
|
+
...(subjectExternalId ? { subject_external_id: subjectExternalId } : {}),
|
|
75
|
+
...(table ? { table_id: table } : {}),
|
|
76
|
+
...(row ? { row_id: row } : {}),
|
|
77
|
+
...(override ? { guardrail_overrides: override } : {}),
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}))
|
|
82
|
+
.addCommand(new Command("override")
|
|
83
|
+
.description("Waive a guardrail for ONE lead — the exception path for a prospect who asked to be called outside their window, or one whose timezone you know you do not have. Waivable: calling_window, unknown_timezone, daily_cap. NEVER waivable: do-not-call, credits, number health, and destination permissions (no flag makes a carrier accept a call it rejects). The waiver replaces any previous one; pass --guardrail none to clear it. The dial still records that the check failed and was waived. A lead can be blocked by more than one thing, so check still_blocked_by in the response — a waiver that did not make the lead dialable still succeeds. Consumes 0 credits.")
|
|
84
|
+
.requiredOption("--task <id>", "The call task to waive a guardrail on.")
|
|
85
|
+
.requiredOption("--guardrail <name>", "calling_window (waive the 09:00-20:00 local calling window) | unknown_timezone (dial without a known local time) | daily_cap (dial past the number's cap for today) | none (clear every waiver).")
|
|
86
|
+
.option("--json", "Print a JSON envelope.")
|
|
87
|
+
.action(async (options) => {
|
|
88
|
+
await handleAsyncAction("voice tasks override", options, () => {
|
|
89
|
+
const task = readOption(options.task);
|
|
90
|
+
if (!task)
|
|
91
|
+
throw new Error("--task is required.");
|
|
92
|
+
const guardrail = readOption(options.guardrail);
|
|
93
|
+
if (!guardrail)
|
|
94
|
+
throw new Error("--guardrail is required.");
|
|
95
|
+
return requestOxygen("/api/cli/voice/tasks", {
|
|
96
|
+
method: "POST",
|
|
97
|
+
body: {
|
|
98
|
+
action: "override",
|
|
99
|
+
task_id: task,
|
|
100
|
+
// "none" is the documented way to clear every waiver; the
|
|
101
|
+
// server treats an empty list as "replace with nothing".
|
|
102
|
+
guardrail_overrides: guardrail === "none" ? [] : guardrail,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}))
|
|
107
|
+
.addCommand(new Command("list")
|
|
108
|
+
.description("List the call queue, best-first (highest priority, then earliest due). Defaults to every status; filter with --status.")
|
|
109
|
+
.option("--status <statuses>", "Comma-separated: pending, claimed, completed, skipped, canceled.")
|
|
110
|
+
.option("--source <source>", "Filter by origin: sequence, crm, table, reply, manual.")
|
|
111
|
+
.option("--sequence <id>", "Only tasks queued by this sequence.")
|
|
112
|
+
.option("--limit <n>", "Maximum tasks to return (1-200; default 50).")
|
|
113
|
+
.option("--json", "Print a JSON envelope.")
|
|
114
|
+
.action(async (options) => {
|
|
115
|
+
await handleAsyncAction("voice tasks list", options, () => {
|
|
116
|
+
const params = new URLSearchParams();
|
|
117
|
+
const status = readOption(options.status);
|
|
118
|
+
if (status)
|
|
119
|
+
params.set("status", status);
|
|
120
|
+
const source = readOption(options.source);
|
|
121
|
+
if (source)
|
|
122
|
+
params.set("source", source);
|
|
123
|
+
const sequence = readOption(options.sequence);
|
|
124
|
+
if (sequence)
|
|
125
|
+
params.set("sequence_id", sequence);
|
|
126
|
+
const limit = readOption(options.limit);
|
|
127
|
+
if (limit)
|
|
128
|
+
params.set("limit", limit);
|
|
129
|
+
const suffix = params.toString();
|
|
130
|
+
return requestOxygen(`/api/cli/voice/tasks${suffix ? `?${suffix}` : ""}`);
|
|
131
|
+
});
|
|
132
|
+
}))
|
|
133
|
+
.addCommand(new Command("claim")
|
|
134
|
+
.description("Take the next due call task, or a specific one with --task. Returns a lease_token you must pass to `complete` — the lease is what stops a second rep dialing the same prospect. A specific task already held by someone else fails with 409 rather than being stolen.")
|
|
135
|
+
.option("--task <id>", "Claim this specific task instead of the next due one.")
|
|
136
|
+
.option("--sequence <id>", "Only claim from this sequence's queue.")
|
|
137
|
+
.option("--json", "Print a JSON envelope.")
|
|
138
|
+
.action(async (options) => {
|
|
139
|
+
await handleAsyncAction("voice tasks claim", options, () => {
|
|
140
|
+
const task = readOption(options.task);
|
|
141
|
+
const sequence = readOption(options.sequence);
|
|
142
|
+
return requestOxygen("/api/cli/voice/tasks", {
|
|
143
|
+
method: "POST",
|
|
144
|
+
body: {
|
|
145
|
+
action: "claim",
|
|
146
|
+
...(task ? { task_id: task } : {}),
|
|
147
|
+
...(sequence ? { sequence_id: sequence } : {}),
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}))
|
|
152
|
+
.addCommand(new Command("complete")
|
|
153
|
+
.description("Record the outcome of a claimed call. Lease-guarded: a lease reclaimed mid-call fails with 409 rather than overwriting the new holder's outcome.")
|
|
154
|
+
.requiredOption("--task <id>", "The claimed task's id.")
|
|
155
|
+
.requiredOption("--lease <token>", "The lease_token returned by `claim`.")
|
|
156
|
+
.requiredOption("--disposition <value>", "connected, no_answer, voicemail, busy, wrong_number, not_interested, callback_requested, meeting_booked, do_not_call, failed.")
|
|
157
|
+
.option("--notes <text>", "Free-text call notes.")
|
|
158
|
+
.option("--json", "Print a JSON envelope.")
|
|
159
|
+
.action(async (options) => {
|
|
160
|
+
await handleAsyncAction("voice tasks complete", options, () => {
|
|
161
|
+
const task = readOption(options.task);
|
|
162
|
+
if (!task)
|
|
163
|
+
throw new Error("--task is required.");
|
|
164
|
+
const lease = readOption(options.lease);
|
|
165
|
+
if (!lease)
|
|
166
|
+
throw new Error("--lease is required.");
|
|
167
|
+
const disposition = readOption(options.disposition);
|
|
168
|
+
if (!disposition)
|
|
169
|
+
throw new Error("--disposition is required.");
|
|
170
|
+
const notes = readOption(options.notes);
|
|
171
|
+
return requestOxygen("/api/cli/voice/tasks", {
|
|
172
|
+
method: "POST",
|
|
173
|
+
body: {
|
|
174
|
+
action: "complete",
|
|
175
|
+
task_id: task,
|
|
176
|
+
lease_token: lease,
|
|
177
|
+
disposition,
|
|
178
|
+
...(notes ? { notes } : {}),
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}))
|
|
183
|
+
.addCommand(new Command("extend")
|
|
184
|
+
.description("Renew a claimed task's 15-minute lease. Completing needs a live lease, so a longer call must heartbeat or its outcome and notes are rejected as stale.")
|
|
185
|
+
.requiredOption("--task <id>", "The claimed task's id.")
|
|
186
|
+
.requiredOption("--lease <token>", "The lease_token returned by `claim`.")
|
|
187
|
+
.option("--json", "Print a JSON envelope.")
|
|
188
|
+
.action(async (options) => {
|
|
189
|
+
await handleAsyncAction("voice tasks extend", options, () => {
|
|
190
|
+
const task = readOption(options.task);
|
|
191
|
+
if (!task)
|
|
192
|
+
throw new Error("--task is required.");
|
|
193
|
+
const lease = readOption(options.lease);
|
|
194
|
+
if (!lease)
|
|
195
|
+
throw new Error("--lease is required.");
|
|
196
|
+
return requestOxygen("/api/cli/voice/tasks", {
|
|
197
|
+
method: "POST",
|
|
198
|
+
body: { action: "extend", task_id: task, lease_token: lease },
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
}))
|
|
202
|
+
.addCommand(new Command("skip")
|
|
203
|
+
.description("Drop a task out of the queue without dialing (bad data, a rep pass, a guardrail block). Needs no lease — the guardrail gate skips tasks it never claimed.")
|
|
204
|
+
.requiredOption("--task <id>", "The task to skip.")
|
|
205
|
+
.requiredOption("--reason <text>", "Why it was skipped; stored on the task.")
|
|
206
|
+
.option("--json", "Print a JSON envelope.")
|
|
207
|
+
.action(async (options) => {
|
|
208
|
+
await handleAsyncAction("voice tasks skip", options, () => {
|
|
209
|
+
const task = readOption(options.task);
|
|
210
|
+
if (!task)
|
|
211
|
+
throw new Error("--task is required.");
|
|
212
|
+
const reason = readOption(options.reason);
|
|
213
|
+
if (!reason)
|
|
214
|
+
throw new Error("--reason is required.");
|
|
215
|
+
return requestOxygen("/api/cli/voice/tasks", {
|
|
216
|
+
method: "POST",
|
|
217
|
+
body: { action: "skip", task_id: task, reason },
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
})))
|
|
221
|
+
.addCommand(new Command("numbers")
|
|
222
|
+
.description("The org's dialing pool: list | search | requirements | buy | tag | cap | release. Searching is free; buying previews the recurring monthly charge until you pass --approved.")
|
|
223
|
+
.addCommand(new Command("list")
|
|
224
|
+
.description("List the org's phone numbers with warm-up state, daily cap, and what each one costs per month. Optional --tag narrows to one campaign's numbers.")
|
|
225
|
+
.option("--tag <tags>", "Comma-separated workspace tags — matches phone numbers carrying ANY of these tags (see `oxygen tags list`).")
|
|
226
|
+
.option("--json", "Print a JSON envelope.")
|
|
227
|
+
.action(async (options) => {
|
|
228
|
+
await handleAsyncAction("voice numbers list", options, () => {
|
|
229
|
+
const params = new URLSearchParams();
|
|
230
|
+
const tags = splitCommaList(options.tag);
|
|
231
|
+
if (tags.length > 0)
|
|
232
|
+
params.set("tag", tags.join(","));
|
|
233
|
+
const suffix = params.toString();
|
|
234
|
+
return requestOxygen(`/api/cli/voice/numbers${suffix ? `?${suffix}` : ""}`);
|
|
235
|
+
});
|
|
236
|
+
}))
|
|
237
|
+
.addCommand(new Command("search")
|
|
238
|
+
.description("Find numbers you could buy, by area code or region. Free: searching buys nothing and reserves nothing, so a listed number can be gone by the time you buy it.")
|
|
239
|
+
.option("--area-code <code>", "Area code, e.g. 415 (US/CA).")
|
|
240
|
+
.option("--region <code>", "State or region, e.g. CA.")
|
|
241
|
+
.option("--country <iso>", "ISO country code (default US). Run `voice numbers requirements` first for a country outside US/CA.")
|
|
242
|
+
.option("--limit <n>", "How many to return (1-30; default 10).")
|
|
243
|
+
.option("--json", "Print a JSON envelope.")
|
|
244
|
+
.action(async (options) => {
|
|
245
|
+
await handleAsyncAction("voice numbers search", options, () => {
|
|
246
|
+
const limit = readOption(options.limit);
|
|
247
|
+
return requestOxygen("/api/cli/voice/numbers", {
|
|
248
|
+
method: "POST",
|
|
249
|
+
body: {
|
|
250
|
+
action: "search",
|
|
251
|
+
...voiceNumberFilters(options),
|
|
252
|
+
...(limit ? { limit: Number(limit) } : {}),
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}))
|
|
257
|
+
.addCommand(new Command("requirements")
|
|
258
|
+
.description("What a country demands before it sells a number (business details, documents). Free, and read live from the carrier.")
|
|
259
|
+
.option("--country <iso>", "ISO country code (default US).")
|
|
260
|
+
.option("--end-user <type>", "business (default) or individual.")
|
|
261
|
+
.option("--json", "Print a JSON envelope.")
|
|
262
|
+
.action(async (options) => {
|
|
263
|
+
await handleAsyncAction("voice numbers requirements", options, () => {
|
|
264
|
+
const country = readOption(options.country);
|
|
265
|
+
const endUser = readOption(options.endUser);
|
|
266
|
+
return requestOxygen("/api/cli/voice/numbers", {
|
|
267
|
+
method: "POST",
|
|
268
|
+
body: {
|
|
269
|
+
action: "requirements",
|
|
270
|
+
...(country ? { iso_country: country.toUpperCase() } : {}),
|
|
271
|
+
...(endUser ? { end_user_type: endUser } : {}),
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
}))
|
|
276
|
+
.addCommand(new Command("buy")
|
|
277
|
+
.description("Buy phone numbers for the dialer. A number is a RECURRING monthly charge until you release it, and it starts in warm-up on a reduced daily cap. Previews by default: the preview names the numbers and the monthly credits, and nothing is bought until you pass --approved. At most 10 per order.")
|
|
278
|
+
.option("--count <n>", "How many numbers to buy (1-10). Not needed with --number.")
|
|
279
|
+
.option("--number <e164s>", "Exact numbers to buy, comma-separated (from `voice numbers search`).")
|
|
280
|
+
.option("--area-code <code>", "Pick numbers in this area code.")
|
|
281
|
+
.option("--region <code>", "Pick numbers in this state or region.")
|
|
282
|
+
.option("--country <iso>", "ISO country code (default US).")
|
|
283
|
+
.option("--approved", "Actually buy. Without this you get a preview and nothing is bought or charged.")
|
|
284
|
+
.option("--json", "Print a JSON envelope.")
|
|
285
|
+
.action(async (options) => {
|
|
286
|
+
await handleAsyncAction("voice numbers buy", options, () => {
|
|
287
|
+
const numbers = splitCommaList(options.number);
|
|
288
|
+
const count = readOption(options.count);
|
|
289
|
+
if (numbers.length === 0 && !count)
|
|
290
|
+
throw new Error("Pass --count <n> or --number <e164,...>.");
|
|
291
|
+
return requestOxygen("/api/cli/voice/numbers", {
|
|
292
|
+
method: "POST",
|
|
293
|
+
body: {
|
|
294
|
+
action: "buy",
|
|
295
|
+
...voiceNumberFilters(options),
|
|
296
|
+
...(numbers.length > 0 ? { phone_numbers: numbers } : { count: Number(count) }),
|
|
297
|
+
...(options.approved === true ? { approved: true } : {}),
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}))
|
|
302
|
+
.addCommand(new Command("tag")
|
|
303
|
+
.description("Replace a phone number's workspace tags (whole set; `--tags \"\"` clears) — e.g. pool the numbers dialing one campaign behind its tag. See `oxygen tags list` for the vocabulary.")
|
|
304
|
+
.argument("<number>", "Phone number in E.164 (e.g. +14155550142) or its id.")
|
|
305
|
+
.requiredOption("--tags <tags>", "Comma-separated workspace tags (replaces the whole set; empty clears).")
|
|
306
|
+
.option("--json", "Print a JSON envelope.")
|
|
307
|
+
.action(async (number, options) => {
|
|
308
|
+
await handleAsyncAction("voice numbers tag", options, () => requestOxygen(`/api/cli/voice/numbers/${encodeURIComponent(number)}/tags`, {
|
|
309
|
+
method: "POST",
|
|
310
|
+
body: { tags: splitCommaList(options.tags) },
|
|
311
|
+
}));
|
|
312
|
+
}))
|
|
313
|
+
.addCommand(new Command("cap")
|
|
314
|
+
.description("Adjust one number's daily dial cap. `voice numbers list` shows every number's cap, today's ceiling and dials left.")
|
|
315
|
+
.addCommand(new Command("set")
|
|
316
|
+
.description("Set one phone number's daily dial cap (dials per day, 1-300; new numbers get 100). A warming number still dials at its ramp's ceiling until it has warmed. Consumes no credits. Example: `oxygen voice numbers cap set +14155550142 --daily-cap 150`.")
|
|
317
|
+
.argument("<number>", "Phone number in E.164 (e.g. +14155550142) or its id.")
|
|
318
|
+
.requiredOption("--daily-cap <n>", "New daily dial cap, a whole number from 1 to 300.")
|
|
319
|
+
.option("--json", "Print a JSON envelope.")
|
|
320
|
+
.action(async (number, options) => {
|
|
321
|
+
await handleAsyncAction("voice numbers cap set", options, () => {
|
|
322
|
+
const raw = readOption(options.dailyCap);
|
|
323
|
+
if (!raw)
|
|
324
|
+
throw new Error("--daily-cap is required.");
|
|
325
|
+
return requestOxygen(`/api/cli/voice/numbers/${encodeURIComponent(number)}/cap`, {
|
|
326
|
+
method: "PATCH",
|
|
327
|
+
body: { daily_cap: Number(raw) },
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
})))
|
|
331
|
+
.addCommand(new Command("release")
|
|
332
|
+
.description("Give a number back to the carrier and stop its monthly charge. IRREVERSIBLE — the number returns to the carrier's pool and can be taken by someone else within minutes; you cannot get it back. Previews by default; pass --approved to actually release.")
|
|
333
|
+
.requiredOption("--number <e164>", "The number to release, e.g. +14155550142.")
|
|
334
|
+
.option("--approved", "Actually release it. Without this you get a preview and nothing changes.")
|
|
335
|
+
.option("--json", "Print a JSON envelope.")
|
|
336
|
+
.action(async (options) => {
|
|
337
|
+
await handleAsyncAction("voice numbers release", options, () => {
|
|
338
|
+
const number = readOption(options.number);
|
|
339
|
+
if (!number)
|
|
340
|
+
throw new Error("--number is required.");
|
|
341
|
+
return requestOxygen("/api/cli/voice/numbers", {
|
|
342
|
+
method: "POST",
|
|
343
|
+
body: {
|
|
344
|
+
action: "release",
|
|
345
|
+
phone_number: number,
|
|
346
|
+
...(options.approved === true ? { approved: true } : {}),
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
}))));
|
|
351
|
+
}
|