@aiaiai-pt/martha-cli 0.5.1 → 0.6.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/CHANGELOG.md +1 -7
- package/README.md +9 -36
- package/dist/index.js +157 -99
- package/package.json +1 -1
- package/skills/martha-cli/SKILL.md +7 -37
package/CHANGELOG.md
CHANGED
|
@@ -4,12 +4,6 @@ All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
-
### Changed — 0.5.1 CLI onboarding feedback
|
|
8
|
-
- `martha init` now defaults to the local preset; the hosted Martha cloud profile remains available through explicit `--preset cloud` and uses `keycloak.frank.nomadriver.co`.
|
|
9
|
-
- CLI version output now reads `martha-cli/package.json` instead of a second hardcoded literal.
|
|
10
|
-
- `martha agents create` exposes `--system-prompt` as the primary inline flag and keeps `--prompt` as a compatibility alias.
|
|
11
|
-
- CLI docs and bundled agent skill now explain explicit customer profile setup, `system_prompt`, and the normal chat-client agent grant-as-tool path.
|
|
12
|
-
|
|
13
7
|
### Added — #407 connections command (service-account Drive auth)
|
|
14
8
|
- `martha connections create|list|update|test|delete` — manage Vault-backed integration connections from the CLI (previously admin-UI / raw-curl only). `create` mirrors `POST /api/admin/connections`; `update` mirrors `PUT`.
|
|
15
9
|
- Service-account Google Drive: `martha connections create --integration google_drive --auth-type service_account --credential-value @sa-key.json --config '{"subject":"...","scopes":[...]}'` reads enterprise Shared Drives without CASA (#407). The SA key shape (`type`, `client_email`) is validated before submit; `--credential-value` accepts `@file` / `-` (stdin) / literal.
|
|
@@ -34,7 +28,7 @@ All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https
|
|
|
34
28
|
First-run UX for third-party developers and agent runtimes.
|
|
35
29
|
|
|
36
30
|
### Added
|
|
37
|
-
- `martha init` — interactive wizard that writes a profile to `~/.martha/config.yaml`. Two presets: `cloud` (martha.nomadriver.co +
|
|
31
|
+
- `martha init` — interactive wizard that writes a profile to `~/.martha/config.yaml`. Two presets: `cloud` (martha.nomadriver.co + auth.nomadriver.co) and `local` (localhost:8080 + 8180). Idempotent with `--force`; rejects overwrites without it.
|
|
38
32
|
- `martha doctor` — five-check diagnostic: API health, API/CLI version skew (via the new `GET /api/version` endpoint), Keycloak OIDC discovery, stored token validity, authenticated request. Exits non-zero only on FAIL, not WARN.
|
|
39
33
|
- `martha skill` — prints the bundled `SKILL.md` to stdout. Agent runtimes that don't read filesystems by convention can now seed prompt context with `npx -y @aiaiai-pt/martha-cli@0.3.0 skill | head -200`.
|
|
40
34
|
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npx -y @aiaiai-pt/martha-cli@latest --help
|
|
|
15
15
|
Pin a version when calling from CI or agent runtimes:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npx -y @aiaiai-pt/martha-cli@0.
|
|
18
|
+
npx -y @aiaiai-pt/martha-cli@0.2.0 agents list --json
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
### Install globally
|
|
@@ -31,12 +31,9 @@ Requires Node.js >= 22.
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
# 1. Configure a profile — writes ~/.martha/config.yaml
|
|
34
|
-
martha init # interactive
|
|
35
|
-
martha init --preset
|
|
36
|
-
martha init --no-interactive --name
|
|
37
|
-
--api-url https://martha.acme.example \
|
|
38
|
-
--keycloak-url https://auth.acme.example \
|
|
39
|
-
--keycloak-realm acme # scripted customer profile
|
|
34
|
+
martha init # interactive (defaults to cloud preset)
|
|
35
|
+
martha init --preset local # localhost dev stack
|
|
36
|
+
martha init --no-interactive --name staging --api-url https://... # scripted
|
|
40
37
|
|
|
41
38
|
# 2. Authenticate
|
|
42
39
|
martha auth login # browser flow (PKCE)
|
|
@@ -59,19 +56,15 @@ Full command reference: `martha --help` (or any subcommand `--help`).
|
|
|
59
56
|
Profiles live at `~/.martha/config.yaml`. Each profile defines:
|
|
60
57
|
|
|
61
58
|
```yaml
|
|
62
|
-
current_profile:
|
|
59
|
+
current_profile: default
|
|
63
60
|
profiles:
|
|
64
|
-
|
|
65
|
-
api_url:
|
|
66
|
-
keycloak_url:
|
|
61
|
+
default:
|
|
62
|
+
api_url: https://martha.nomadriver.co
|
|
63
|
+
keycloak_url: https://auth.nomadriver.co
|
|
67
64
|
keycloak_realm: frank
|
|
68
65
|
auth_type: oidc
|
|
69
66
|
```
|
|
70
67
|
|
|
71
|
-
Install does not create or mutate `~/.martha/config.yaml`. Run `martha init`
|
|
72
|
-
or set `MARTHA_API_URL`, `MARTHA_KEYCLOAK_URL`, and
|
|
73
|
-
`MARTHA_KEYCLOAK_REALM` explicitly for your deployment.
|
|
74
|
-
|
|
75
68
|
Override per-command:
|
|
76
69
|
|
|
77
70
|
```bash
|
|
@@ -109,32 +102,12 @@ martha skill
|
|
|
109
102
|
npx -y @aiaiai-pt/martha-cli@latest skill | head -200
|
|
110
103
|
```
|
|
111
104
|
|
|
112
|
-
Agent YAML should use `system_prompt` for the durable agent instruction. To
|
|
113
|
-
make an agent callable from a chat client, grant the agent to that client; the
|
|
114
|
-
chat model will call it as a tool:
|
|
115
|
-
|
|
116
|
-
```yaml
|
|
117
|
-
kind: Agent
|
|
118
|
-
name: support-bot
|
|
119
|
-
system_prompt: You help customers resolve support tickets.
|
|
120
|
-
llm_config:
|
|
121
|
-
provider: anthropic
|
|
122
|
-
model: claude-sonnet-4-6
|
|
123
|
-
loop_config:
|
|
124
|
-
max_iterations: 10
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
martha definitions apply -f support-bot.yaml --yes
|
|
129
|
-
martha clients grant web-chat agent support-bot
|
|
130
|
-
```
|
|
131
|
-
|
|
132
105
|
## Stability
|
|
133
106
|
|
|
134
107
|
`0.x` releases may change CLI flags or JSON output between minor versions. Pin a version in CI/agent contexts:
|
|
135
108
|
|
|
136
109
|
```bash
|
|
137
|
-
npx -y @aiaiai-pt/martha-cli@0.
|
|
110
|
+
npx -y @aiaiai-pt/martha-cli@0.3.0 ...
|
|
138
111
|
```
|
|
139
112
|
|
|
140
113
|
`1.0.0` will commit to a stable contract — see the [tracking issue](https://github.com/westeuropeco/martha/issues/292).
|
package/dist/index.js
CHANGED
|
@@ -5,25 +5,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
+
var __returnValue = (v) => v;
|
|
35
|
+
function __exportSetter(name, newValue) {
|
|
36
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
+
}
|
|
20
38
|
var __export = (target, all) => {
|
|
21
39
|
for (var name in all)
|
|
22
40
|
__defProp(target, name, {
|
|
23
41
|
get: all[name],
|
|
24
42
|
enumerable: true,
|
|
25
43
|
configurable: true,
|
|
26
|
-
set: (
|
|
44
|
+
set: __exportSetter.bind(all, name)
|
|
27
45
|
});
|
|
28
46
|
};
|
|
29
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -1001,7 +1019,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1001
1019
|
this._exitCallback = (err) => {
|
|
1002
1020
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
1003
1021
|
throw err;
|
|
1004
|
-
}
|
|
1022
|
+
}
|
|
1005
1023
|
};
|
|
1006
1024
|
}
|
|
1007
1025
|
return this;
|
|
@@ -11025,31 +11043,7 @@ import { createInterface as createInterface2 } from "node:readline";
|
|
|
11025
11043
|
init_errors();
|
|
11026
11044
|
|
|
11027
11045
|
// src/version.ts
|
|
11028
|
-
|
|
11029
|
-
import path4 from "node:path";
|
|
11030
|
-
import { fileURLToPath } from "node:url";
|
|
11031
|
-
function readPackageVersion() {
|
|
11032
|
-
let dir = path4.dirname(fileURLToPath(import.meta.url));
|
|
11033
|
-
for (let i = 0;i < 5; i += 1) {
|
|
11034
|
-
const packagePath = path4.join(dir, "package.json");
|
|
11035
|
-
if (fs5.existsSync(packagePath)) {
|
|
11036
|
-
try {
|
|
11037
|
-
const parsed = JSON.parse(fs5.readFileSync(packagePath, "utf-8"));
|
|
11038
|
-
if (parsed.name === "@aiaiai-pt/martha-cli" && parsed.version) {
|
|
11039
|
-
return parsed.version;
|
|
11040
|
-
}
|
|
11041
|
-
} catch {
|
|
11042
|
-
return "0.0.0-dev";
|
|
11043
|
-
}
|
|
11044
|
-
}
|
|
11045
|
-
const parent = path4.dirname(dir);
|
|
11046
|
-
if (parent === dir)
|
|
11047
|
-
break;
|
|
11048
|
-
dir = parent;
|
|
11049
|
-
}
|
|
11050
|
-
return "0.0.0-dev";
|
|
11051
|
-
}
|
|
11052
|
-
var CLI_VERSION = readPackageVersion();
|
|
11046
|
+
var CLI_VERSION = "0.3.0";
|
|
11053
11047
|
|
|
11054
11048
|
// src/commands/sessions.ts
|
|
11055
11049
|
function relativeTime(iso) {
|
|
@@ -11111,8 +11105,7 @@ function printSessionTable(sessions) {
|
|
|
11111
11105
|
{ header: "LAST ACTIVE", accessor: (r) => r.active },
|
|
11112
11106
|
{ header: "CLIENT", accessor: (r) => r.client }
|
|
11113
11107
|
];
|
|
11114
|
-
const
|
|
11115
|
-
const stripAnsi = (s) => s.replace(ansiPattern, "");
|
|
11108
|
+
const stripAnsi = (s) => s.replace(/\x1B\[[0-9;]*m/g, "");
|
|
11116
11109
|
const widths = cols.map((col) => Math.max(col.header.length, ...rows.map((r) => stripAnsi(col.accessor(r)).length)));
|
|
11117
11110
|
const header = cols.map((col, i) => col.header.padEnd(widths[i])).join(" ");
|
|
11118
11111
|
console.log(source_default.bold(header));
|
|
@@ -11886,7 +11879,7 @@ function registerConfigCommand(program2) {
|
|
|
11886
11879
|
config.current_profile = name;
|
|
11887
11880
|
}
|
|
11888
11881
|
saveConfig(config);
|
|
11889
|
-
if (program2.opts().json) {
|
|
11882
|
+
if (!!program2.opts().json) {
|
|
11890
11883
|
console.log(JSON.stringify({
|
|
11891
11884
|
name,
|
|
11892
11885
|
profile,
|
|
@@ -11930,7 +11923,7 @@ function registerConfigCommand(program2) {
|
|
|
11930
11923
|
config.current_profile = remaining[0] ?? "default";
|
|
11931
11924
|
}
|
|
11932
11925
|
saveConfig(config);
|
|
11933
|
-
if (program2.opts().json) {
|
|
11926
|
+
if (!!program2.opts().json) {
|
|
11934
11927
|
console.log(JSON.stringify({ name, deleted: true }));
|
|
11935
11928
|
return;
|
|
11936
11929
|
}
|
|
@@ -11939,8 +11932,8 @@ function registerConfigCommand(program2) {
|
|
|
11939
11932
|
}
|
|
11940
11933
|
|
|
11941
11934
|
// src/commands/definitions-apply.ts
|
|
11942
|
-
import
|
|
11943
|
-
import
|
|
11935
|
+
import fs5 from "node:fs";
|
|
11936
|
+
import path4 from "node:path";
|
|
11944
11937
|
init_dist();
|
|
11945
11938
|
init_errors();
|
|
11946
11939
|
var VALID_KINDS = new Set(["Function", "Workflow", "Agent"]);
|
|
@@ -11975,20 +11968,20 @@ var SERVER_GENERATED_FIELDS = new Set([
|
|
|
11975
11968
|
]);
|
|
11976
11969
|
var AGENT_MANAGED_FIELDS = new Set(["functions", "workflows"]);
|
|
11977
11970
|
function loadDefinitions(inputPath) {
|
|
11978
|
-
const resolved =
|
|
11979
|
-
if (!
|
|
11971
|
+
const resolved = path4.resolve(inputPath);
|
|
11972
|
+
if (!fs5.existsSync(resolved)) {
|
|
11980
11973
|
throw new CLIError(`Path not found: ${inputPath}`, 1 /* Error */);
|
|
11981
11974
|
}
|
|
11982
|
-
const stat =
|
|
11975
|
+
const stat = fs5.statSync(resolved);
|
|
11983
11976
|
if (stat.isDirectory()) {
|
|
11984
11977
|
return loadDirectory(resolved);
|
|
11985
11978
|
}
|
|
11986
11979
|
return loadFile(resolved);
|
|
11987
11980
|
}
|
|
11988
11981
|
function loadDirectory(dirPath) {
|
|
11989
|
-
const entries =
|
|
11982
|
+
const entries = fs5.readdirSync(dirPath).sort();
|
|
11990
11983
|
const validExts = new Set([".yaml", ".yml", ".json"]);
|
|
11991
|
-
const files = entries.filter((e) => validExts.has(
|
|
11984
|
+
const files = entries.filter((e) => validExts.has(path4.extname(e).toLowerCase())).map((e) => path4.join(dirPath, e));
|
|
11992
11985
|
if (files.length === 0) {
|
|
11993
11986
|
throw new CLIError(`No YAML or JSON files found in ${dirPath}`, 4 /* Validation */);
|
|
11994
11987
|
}
|
|
@@ -11999,8 +11992,8 @@ function loadDirectory(dirPath) {
|
|
|
11999
11992
|
return results;
|
|
12000
11993
|
}
|
|
12001
11994
|
function loadFile(filePath) {
|
|
12002
|
-
const content =
|
|
12003
|
-
const ext =
|
|
11995
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
11996
|
+
const ext = path4.extname(filePath).toLowerCase();
|
|
12004
11997
|
if (ext === ".json") {
|
|
12005
11998
|
const parsed = parseJsonFile(content, filePath);
|
|
12006
11999
|
return [toLocalDefinition(parsed, filePath)];
|
|
@@ -12422,7 +12415,7 @@ Examples:
|
|
|
12422
12415
|
}
|
|
12423
12416
|
|
|
12424
12417
|
// src/commands/definitions-export.ts
|
|
12425
|
-
import
|
|
12418
|
+
import fs6 from "node:fs";
|
|
12426
12419
|
init_errors();
|
|
12427
12420
|
async function exportDefinitions(ctx, opts, isJsonMode) {
|
|
12428
12421
|
const params = {};
|
|
@@ -12438,7 +12431,7 @@ async function exportDefinitions(ctx, opts, isJsonMode) {
|
|
|
12438
12431
|
const text = await res.text();
|
|
12439
12432
|
if (opts.output) {
|
|
12440
12433
|
try {
|
|
12441
|
-
|
|
12434
|
+
fs6.writeFileSync(opts.output, text);
|
|
12442
12435
|
} catch (err) {
|
|
12443
12436
|
throw new CLIError(`Failed to write ${opts.output}: ${err instanceof Error ? err.message : String(err)}`, 1 /* Error */);
|
|
12444
12437
|
}
|
|
@@ -13036,7 +13029,6 @@ Usage: martha workflows execute ${name} --inputs '${JSON.stringify(Object.fromEn
|
|
|
13036
13029
|
label: n.label || "-",
|
|
13037
13030
|
connections: (outgoing.get(n.id) ?? []).join(", ") || "-"
|
|
13038
13031
|
}));
|
|
13039
|
-
const ansiPattern = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
13040
13032
|
const cols = [
|
|
13041
13033
|
{ header: "ID", accessor: (r) => r.id, raw: (r) => r.id },
|
|
13042
13034
|
{ header: "TYPE", accessor: (r) => r.type, raw: (r) => r.type },
|
|
@@ -13044,7 +13036,7 @@ Usage: martha workflows execute ${name} --inputs '${JSON.stringify(Object.fromEn
|
|
|
13044
13036
|
{
|
|
13045
13037
|
header: "CONNECTIONS",
|
|
13046
13038
|
accessor: (r) => r.connections,
|
|
13047
|
-
raw: (r) => (outgoing.get(r.id) ?? []).map((t) => t.replace(
|
|
13039
|
+
raw: (r) => (outgoing.get(r.id) ?? []).map((t) => t.replace(/\x1B\[[0-9;]*m/g, "")).join(", ") || "-"
|
|
13048
13040
|
}
|
|
13049
13041
|
];
|
|
13050
13042
|
const widths = cols.map((col) => Math.max(col.header.length, ...rows.map((r) => col.raw(r).length)));
|
|
@@ -13162,8 +13154,8 @@ function registerProjectionCommands(parentCmd, getCtx, isJson) {
|
|
|
13162
13154
|
} catch {}
|
|
13163
13155
|
}
|
|
13164
13156
|
if (opts.output) {
|
|
13165
|
-
const
|
|
13166
|
-
await
|
|
13157
|
+
const fs7 = await import("node:fs/promises");
|
|
13158
|
+
await fs7.writeFile(opts.output, toWrite, "utf-8");
|
|
13167
13159
|
if (!isJson()) {
|
|
13168
13160
|
console.error(source_default.dim(`Wrote ${format} projection of '${name}' to ${opts.output}`));
|
|
13169
13161
|
}
|
|
@@ -13326,20 +13318,16 @@ var agentsConfig = {
|
|
|
13326
13318
|
},
|
|
13327
13319
|
normalizeBody: normalizeAgentBody,
|
|
13328
13320
|
extraCreateOptions: (cmd) => {
|
|
13329
|
-
cmd.option("--name <name>", "Agent name").option("--type <type>", "Agent type (cloud or external)", "cloud").option("--model <model>", "LLM model (e.g. anthropic/claude-sonnet-4-6)").option("--provider <provider>", "LLM provider (anthropic, openai)").option("--
|
|
13321
|
+
cmd.option("--name <name>", "Agent name").option("--type <type>", "Agent type (cloud or external)", "cloud").option("--model <model>", "LLM model (e.g. anthropic/claude-sonnet-4-6)").option("--provider <provider>", "LLM provider (anthropic, openai)").option("--prompt <text>", "System prompt").option("--description <text>", "Agent description").option("--temperature <n>", "Temperature (0-1)").option("--max-tokens <n>", "Max output tokens").option("--tags <tags>", "Capability domains (comma-separated)").option("--local-tools <tools>", "Local tools (comma-separated)").option("--auth <method>", "Auth method for external agents: service-account or api-key");
|
|
13330
13322
|
},
|
|
13331
13323
|
buildInlineBody: (opts) => {
|
|
13332
13324
|
if (!opts.name)
|
|
13333
13325
|
return null;
|
|
13334
|
-
if (opts.systemPrompt && opts.prompt) {
|
|
13335
|
-
throw new CLIError("Use only one of --system-prompt or --prompt.", 4 /* Validation */, "--prompt is a backwards-compatible alias; prefer --system-prompt.");
|
|
13336
|
-
}
|
|
13337
13326
|
const body = { name: opts.name };
|
|
13338
13327
|
if (opts.description)
|
|
13339
13328
|
body.description = opts.description;
|
|
13340
|
-
|
|
13341
|
-
|
|
13342
|
-
body.system_prompt = systemPrompt;
|
|
13329
|
+
if (opts.prompt)
|
|
13330
|
+
body.system_prompt = opts.prompt;
|
|
13343
13331
|
if (opts.type)
|
|
13344
13332
|
body.agent_type = opts.type;
|
|
13345
13333
|
if (opts.model)
|
|
@@ -13531,7 +13519,7 @@ Usage:
|
|
|
13531
13519
|
};
|
|
13532
13520
|
|
|
13533
13521
|
// src/commands/documents.ts
|
|
13534
|
-
import
|
|
13522
|
+
import fs7 from "node:fs";
|
|
13535
13523
|
init_errors();
|
|
13536
13524
|
var TERMINAL_STATUSES2 = new Set(["ready", "error"]);
|
|
13537
13525
|
var SPINNER_FRAMES2 = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -13777,6 +13765,7 @@ ${items.length} collections`));
|
|
|
13777
13765
|
console.log(` Status: ${col.is_active !== false ? source_default.green("active") : source_default.dim("inactive")}`);
|
|
13778
13766
|
console.log(` Documents: ${col.document_count ?? 0}`);
|
|
13779
13767
|
console.log(` Storage: ${col.storage_backend ?? "-"}`);
|
|
13768
|
+
console.log(` Drive: ${col.drive_folder_id ? source_default.cyan(col.drive_folder_id) : source_default.dim("not linked")}`);
|
|
13780
13769
|
if (col.total_size_bytes != null) {
|
|
13781
13770
|
console.log(` Size: ${formatBytes(col.total_size_bytes)}`);
|
|
13782
13771
|
}
|
|
@@ -13832,7 +13821,7 @@ ${items.length} collections`));
|
|
|
13832
13821
|
});
|
|
13833
13822
|
cmd.command("upload <collection-id> <file>").description("Upload a document to a collection").option("--wait", "Wait for ingestion to complete").option("--follow", "Follow ingestion progress in real time").action(async (collectionId, filePath, opts) => {
|
|
13834
13823
|
const ctx = getCtx();
|
|
13835
|
-
if (!
|
|
13824
|
+
if (!fs7.existsSync(filePath)) {
|
|
13836
13825
|
throw new CLIError(`File not found: ${filePath}`, 4 /* Validation */);
|
|
13837
13826
|
}
|
|
13838
13827
|
const result = await ctx.api.upload(`/api/admin/collections/${encodeURIComponent(collectionId)}/documents`, filePath);
|
|
@@ -14151,6 +14140,7 @@ ${source_default.bold(`Sources (${result.sources.length} chunks):`)}`);
|
|
|
14151
14140
|
|
|
14152
14141
|
// src/commands/document-sync.ts
|
|
14153
14142
|
init_errors();
|
|
14143
|
+
var VALID_MODES = ["polling", "evented", "manual"];
|
|
14154
14144
|
function registerDocumentSyncCommands(program2) {
|
|
14155
14145
|
const cmd = program2.command("document-sync").description("Manage durable document sync sources (Google Drive, etc.)");
|
|
14156
14146
|
function getCtx() {
|
|
@@ -14185,17 +14175,17 @@ ${name}${tag}`));
|
|
|
14185
14175
|
throw new CLIError("--source and --all are mutually exclusive", 4 /* Validation */);
|
|
14186
14176
|
}
|
|
14187
14177
|
const params = { dry_run: String(dryRun) };
|
|
14188
|
-
let
|
|
14178
|
+
let sources2;
|
|
14189
14179
|
if (opts.all) {
|
|
14190
14180
|
const all = await ctx.api.get("/api/admin/document-sync/sources", { params: { provider: "google_drive" } });
|
|
14191
|
-
|
|
14181
|
+
sources2 = all;
|
|
14192
14182
|
} else {
|
|
14193
|
-
|
|
14183
|
+
sources2 = [
|
|
14194
14184
|
{ id: opts.source, name: opts.source, provider: "google_drive" }
|
|
14195
14185
|
];
|
|
14196
14186
|
}
|
|
14197
14187
|
const results = [];
|
|
14198
|
-
for (const src of
|
|
14188
|
+
for (const src of sources2) {
|
|
14199
14189
|
const summary = await ctx.api.post(`/api/admin/document-sync/sources/${encodeURIComponent(src.id)}/reconcile-tree`, undefined, { params });
|
|
14200
14190
|
results.push(summary);
|
|
14201
14191
|
if (!isJson())
|
|
@@ -14210,6 +14200,75 @@ ${name}${tag}`));
|
|
|
14210
14200
|
No google_drive sources found.`));
|
|
14211
14201
|
}
|
|
14212
14202
|
});
|
|
14203
|
+
const sources = cmd.command("sources").description("Create, list, and run document sync sources");
|
|
14204
|
+
sources.command("list").description("List document sync sources for the tenant").option("--provider <name>", "Filter by provider (e.g. s3_compatible_folder)").action(async (opts) => {
|
|
14205
|
+
const ctx = getCtx();
|
|
14206
|
+
const params = {};
|
|
14207
|
+
if (opts.provider)
|
|
14208
|
+
params.provider = opts.provider;
|
|
14209
|
+
const rows = await ctx.api.get("/api/admin/document-sync/sources", { params });
|
|
14210
|
+
if (isJson()) {
|
|
14211
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
14212
|
+
return;
|
|
14213
|
+
}
|
|
14214
|
+
if (rows.length === 0) {
|
|
14215
|
+
console.log(source_default.dim("No document sync sources found."));
|
|
14216
|
+
return;
|
|
14217
|
+
}
|
|
14218
|
+
for (const s of rows) {
|
|
14219
|
+
console.log(` ${source_default.cyan((s.provider ?? "?").padEnd(22))} ${s.name} ` + source_default.dim(`(id=${s.id}, profile=${s.provider_profile ?? "-"}, ` + `mode=${s.mode ?? "-"}, status=${s.status ?? "-"})`));
|
|
14220
|
+
}
|
|
14221
|
+
});
|
|
14222
|
+
sources.command("create").description("Create a document sync source. For s3_compatible_folder pass " + "--bucket (+ --endpoint-url for custom_s3) and a --connection holding " + "the S3 credentials.").requiredOption("--provider <name>", "Provider: s3_compatible_folder | google_drive").requiredOption("--name <name>", "Source name (unique per tenant)").requiredOption("--collection <id>", "Target collection id (objects ingest here)").option("--connection <id>", "Connection id holding the source credentials (required for a " + "customer-owned s3 bucket)").option("--profile <name>", "s3 profile: custom_s3 | cloudflare_r2").option("--bucket <name>", "s3 bucket name (s3_compatible_folder)").option("--endpoint-url <url>", "s3 endpoint URL (required for custom_s3 / a customer bucket)").option("--prefix <prefix>", "s3 key prefix to sync (optional)").option("--mode <mode>", `Sync mode: ${VALID_MODES.join(" | ")}`, "manual").action(async (opts) => {
|
|
14223
|
+
if (!VALID_MODES.includes(opts.mode)) {
|
|
14224
|
+
throw new CLIError(`--mode must be one of: ${VALID_MODES.join(", ")}`, 4 /* Validation */);
|
|
14225
|
+
}
|
|
14226
|
+
const body = {
|
|
14227
|
+
provider: opts.provider,
|
|
14228
|
+
name: opts.name,
|
|
14229
|
+
target_collection_id: opts.collection,
|
|
14230
|
+
mode: opts.mode
|
|
14231
|
+
};
|
|
14232
|
+
if (opts.connection)
|
|
14233
|
+
body.connection_id = opts.connection;
|
|
14234
|
+
if (opts.provider === "s3_compatible_folder") {
|
|
14235
|
+
if (!opts.bucket) {
|
|
14236
|
+
throw new CLIError("--bucket is required for s3_compatible_folder.", 4 /* Validation */);
|
|
14237
|
+
}
|
|
14238
|
+
const profile = opts.profile ?? "custom_s3";
|
|
14239
|
+
if (profile === "custom_s3" && !opts.endpointUrl) {
|
|
14240
|
+
throw new CLIError("--endpoint-url is required for the custom_s3 profile.", 4 /* Validation */);
|
|
14241
|
+
}
|
|
14242
|
+
if (opts.connection && !opts.endpointUrl) {
|
|
14243
|
+
throw new CLIError("--endpoint-url is required for a customer-owned bucket source " + "(one with --connection).", 4 /* Validation */);
|
|
14244
|
+
}
|
|
14245
|
+
body.provider_profile = profile;
|
|
14246
|
+
const root = { bucket: opts.bucket };
|
|
14247
|
+
if (opts.endpointUrl)
|
|
14248
|
+
root.endpoint_url = opts.endpointUrl;
|
|
14249
|
+
if (opts.prefix)
|
|
14250
|
+
root.prefix = opts.prefix;
|
|
14251
|
+
body.root_locator = root;
|
|
14252
|
+
}
|
|
14253
|
+
const ctx = getCtx();
|
|
14254
|
+
const resp = await ctx.api.post("/api/admin/document-sync/sources", body);
|
|
14255
|
+
if (isJson()) {
|
|
14256
|
+
console.log(JSON.stringify(resp, null, 2));
|
|
14257
|
+
return;
|
|
14258
|
+
}
|
|
14259
|
+
console.log(source_default.green(`Created ${opts.provider} source '${opts.name}' ` + `(id=${resp.id}, mode=${resp.mode ?? opts.mode}). ` + `Run it with: martha document-sync sources reconcile ${resp.id}`));
|
|
14260
|
+
});
|
|
14261
|
+
for (const op of ["run", "reconcile"]) {
|
|
14262
|
+
sources.command(`${op} <source_id>`).description(op === "reconcile" ? "Reconcile a source (full diff: ingest new/changed, soft-delete gone)" : "Run a one-off sync for a source").action(async (sourceId) => {
|
|
14263
|
+
const ctx = getCtx();
|
|
14264
|
+
const resp = await ctx.api.post(`/api/admin/document-sync/sources/${encodeURIComponent(sourceId)}/${op}`, undefined);
|
|
14265
|
+
if (isJson()) {
|
|
14266
|
+
console.log(JSON.stringify(resp, null, 2));
|
|
14267
|
+
return;
|
|
14268
|
+
}
|
|
14269
|
+
console.log(source_default.green(`Started ${op} for source ${sourceId} ` + `(workflow=${resp.workflow_id}, status=${resp.status})`));
|
|
14270
|
+
});
|
|
14271
|
+
}
|
|
14213
14272
|
}
|
|
14214
14273
|
|
|
14215
14274
|
// src/commands/approvals.ts
|
|
@@ -15278,8 +15337,8 @@ ${data.length} spec(s)`));
|
|
|
15278
15337
|
Resources:
|
|
15279
15338
|
`));
|
|
15280
15339
|
for (const r of resources) {
|
|
15281
|
-
const
|
|
15282
|
-
console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${
|
|
15340
|
+
const path5 = r.path || `/${r.name}`;
|
|
15341
|
+
console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path5}`));
|
|
15283
15342
|
}
|
|
15284
15343
|
}
|
|
15285
15344
|
try {
|
|
@@ -15299,14 +15358,14 @@ Functions:
|
|
|
15299
15358
|
console.log(source_default.dim(`
|
|
15300
15359
|
Proxy: martha integrations proxy ${name} GET /<path>`));
|
|
15301
15360
|
});
|
|
15302
|
-
cmd.command("proxy <name> <method> <path>").description("Send a request through the plugin proxy").option("--data <json>", "JSON request body").option("--query <params>", "Query params as key=val&key=val").action(async (name, method,
|
|
15361
|
+
cmd.command("proxy <name> <method> <path>").description("Send a request through the plugin proxy").option("--data <json>", "JSON request body").option("--query <params>", "Query params as key=val&key=val").action(async (name, method, path5, opts) => {
|
|
15303
15362
|
const ctx = getCtx();
|
|
15304
15363
|
const upperMethod = method.toUpperCase();
|
|
15305
15364
|
const allowed = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
15306
15365
|
if (!allowed.has(upperMethod)) {
|
|
15307
15366
|
throw new CLIError(`Invalid method: ${method}`, 4 /* Validation */, "Allowed: GET, POST, PUT, PATCH, DELETE");
|
|
15308
15367
|
}
|
|
15309
|
-
const cleanPath =
|
|
15368
|
+
const cleanPath = path5.startsWith("/") ? path5.slice(1) : path5;
|
|
15310
15369
|
const proxyUrl = `/api/admin/plugins/${encodeURIComponent(name)}/${cleanPath}`;
|
|
15311
15370
|
const params = {};
|
|
15312
15371
|
if (opts.query) {
|
|
@@ -15406,7 +15465,7 @@ Connections`));
|
|
|
15406
15465
|
}
|
|
15407
15466
|
console.log();
|
|
15408
15467
|
});
|
|
15409
|
-
cmd.command("create").description("Create a connection. For service_account, --credential-value is the SA " + "JSON key (use '@path' to read a file or '-' for stdin) and --config " + `carries non-secret settings, e.g. '{"subject":"u@corp.com","scopes":["https://www.googleapis.com/auth/drive.readonly"]}'. ` + "OAuth2 connections must be created in the admin UI (browser consent).").requiredOption("--integration <name>", "Integration name (e.g. google_drive)").requiredOption("--name <name>", "Connection name (unique per integration)").option("--auth-type <type>", "Auth type: api_key | bearer | basic | service_account", "api_key").option("--credential-value <value>", "Secret material. For service_account: the SA JSON key. " + "Use '-' to read stdin, '@path' to read a file.").option("--config <json>", "Non-secret config JSON object (stored in Postgres, not Vault)").option("--scope <scope>", "Connection scope (tenant|client|system)", "tenant").option("--scope-ref <ref>", "Scope reference (required for client scope)").option("--not-default", "Do not mark as default for this integration").action(async (opts) => {
|
|
15468
|
+
cmd.command("create").description("Create a connection. For service_account, --credential-value is the SA " + "JSON key (use '@path' to read a file or '-' for stdin) and --config " + `carries non-secret settings, e.g. '{"subject":"u@corp.com","scopes":["https://www.googleapis.com/auth/drive.readonly"]}'. ` + "OAuth2 connections must be created in the admin UI (browser consent).").requiredOption("--integration <name>", "Integration name (e.g. google_drive)").requiredOption("--name <name>", "Connection name (unique per integration)").option("--auth-type <type>", "Auth type: api_key | bearer | basic | service_account | aws_access_key " + `(s3: --credential-value '{"access_key_id":"…","secret_access_key":"…"}')`, "api_key").option("--credential-value <value>", "Secret material. For service_account: the SA JSON key. " + "Use '-' to read stdin, '@path' to read a file.").option("--config <json>", "Non-secret config JSON object (stored in Postgres, not Vault)").option("--scope <scope>", "Connection scope (tenant|client|system)", "tenant").option("--scope-ref <ref>", "Scope reference (required for client scope)").option("--not-default", "Do not mark as default for this integration").action(async (opts) => {
|
|
15410
15469
|
if (opts.authType === "oauth2") {
|
|
15411
15470
|
throw new CLIError("OAuth2 connections cannot be created from the CLI — they require " + "an interactive browser consent flow.", 4 /* Validation */, "Create OAuth2 connections in the admin UI under Integrations → Connections.");
|
|
15412
15471
|
}
|
|
@@ -15518,8 +15577,8 @@ async function resolveCredentialValue(value) {
|
|
|
15518
15577
|
if (value === "-")
|
|
15519
15578
|
return readStdin();
|
|
15520
15579
|
if (value.startsWith("@")) {
|
|
15521
|
-
const
|
|
15522
|
-
return (await
|
|
15580
|
+
const fs8 = await import("node:fs/promises");
|
|
15581
|
+
return (await fs8.readFile(value.slice(1), "utf-8")).trim();
|
|
15523
15582
|
}
|
|
15524
15583
|
return value;
|
|
15525
15584
|
}
|
|
@@ -15634,8 +15693,8 @@ Notification connections`));
|
|
|
15634
15693
|
if (credentialValue === "-") {
|
|
15635
15694
|
credentialValue = await readStdin2();
|
|
15636
15695
|
} else if (credentialValue?.startsWith("@")) {
|
|
15637
|
-
const
|
|
15638
|
-
credentialValue = await
|
|
15696
|
+
const fs8 = await import("node:fs/promises");
|
|
15697
|
+
credentialValue = await fs8.readFile(credentialValue.slice(1), "utf-8");
|
|
15639
15698
|
}
|
|
15640
15699
|
if (!credentialValue) {
|
|
15641
15700
|
throw new Error("--credential-value is required (use '-' for stdin or '@path' for file).");
|
|
@@ -15685,11 +15744,11 @@ async function readStdin2() {
|
|
|
15685
15744
|
|
|
15686
15745
|
// src/commands/messaging.ts
|
|
15687
15746
|
init_errors();
|
|
15688
|
-
async function messagingFetch(baseUrl,
|
|
15689
|
-
if (/^(https?:)?\/\//i.test(
|
|
15747
|
+
async function messagingFetch(baseUrl, path5, opts) {
|
|
15748
|
+
if (/^(https?:)?\/\//i.test(path5)) {
|
|
15690
15749
|
throw new CLIError("Absolute URL paths are not allowed", 1 /* Error */);
|
|
15691
15750
|
}
|
|
15692
|
-
const url = new URL(
|
|
15751
|
+
const url = new URL(path5, baseUrl);
|
|
15693
15752
|
const headers = {
|
|
15694
15753
|
"Content-Type": "application/json"
|
|
15695
15754
|
};
|
|
@@ -16979,7 +17038,7 @@ ${models.length} models`));
|
|
|
16979
17038
|
}
|
|
16980
17039
|
|
|
16981
17040
|
// src/commands/wiki.ts
|
|
16982
|
-
import
|
|
17041
|
+
import fs8 from "node:fs";
|
|
16983
17042
|
init_errors();
|
|
16984
17043
|
function registerWikiCommands(program2) {
|
|
16985
17044
|
const cmd = program2.command("wiki").description("Manage tenant wiki pages, settings, schema, recompile (#245 D5.4)");
|
|
@@ -17026,14 +17085,14 @@ function registerWikiCommands(program2) {
|
|
|
17026
17085
|
console.log(source_default.dim(`
|
|
17027
17086
|
${pages.length} pages`));
|
|
17028
17087
|
});
|
|
17029
|
-
cmd.command("get <path>").description("Fetch the raw markdown body of a wiki page").option("--out <file>", "Write body to file instead of stdout").action(async (
|
|
17088
|
+
cmd.command("get <path>").description("Fetch the raw markdown body of a wiki page").option("--out <file>", "Write body to file instead of stdout").action(async (path5, opts) => {
|
|
17030
17089
|
const ctx = getCtx();
|
|
17031
|
-
const safe =
|
|
17090
|
+
const safe = path5.split("/").map(encodeURIComponent).join("/");
|
|
17032
17091
|
const resp = await ctx.api.getRaw(`/api/wiki/pages/${safe}`, {
|
|
17033
17092
|
headers: { Accept: "text/markdown,*/*" }
|
|
17034
17093
|
});
|
|
17035
17094
|
if (resp.status === 404) {
|
|
17036
|
-
throw new CLIError(`page not found: ${
|
|
17095
|
+
throw new CLIError(`page not found: ${path5}`, 3 /* NotFound */);
|
|
17037
17096
|
}
|
|
17038
17097
|
if (!resp.ok) {
|
|
17039
17098
|
const detail = await resp.text();
|
|
@@ -17041,16 +17100,16 @@ ${pages.length} pages`));
|
|
|
17041
17100
|
}
|
|
17042
17101
|
const body = await resp.text();
|
|
17043
17102
|
if (opts.out) {
|
|
17044
|
-
|
|
17103
|
+
fs8.writeFileSync(opts.out, body);
|
|
17045
17104
|
if (!isJson()) {
|
|
17046
17105
|
console.log(source_default.dim(`wrote ${body.length} bytes to ${opts.out}`));
|
|
17047
17106
|
} else {
|
|
17048
|
-
console.log(JSON.stringify({ path:
|
|
17107
|
+
console.log(JSON.stringify({ path: path5, bytes: body.length, file: opts.out }));
|
|
17049
17108
|
}
|
|
17050
17109
|
return;
|
|
17051
17110
|
}
|
|
17052
17111
|
if (isJson()) {
|
|
17053
|
-
console.log(JSON.stringify({ path:
|
|
17112
|
+
console.log(JSON.stringify({ path: path5, body, etag: resp.headers.get("ETag") }));
|
|
17054
17113
|
} else {
|
|
17055
17114
|
process.stdout.write(body);
|
|
17056
17115
|
}
|
|
@@ -17145,10 +17204,10 @@ ${pages.length} pages`));
|
|
|
17145
17204
|
}
|
|
17146
17205
|
if (opts.compilePromptOverrideFile) {
|
|
17147
17206
|
const file = String(opts.compilePromptOverrideFile);
|
|
17148
|
-
if (!
|
|
17207
|
+
if (!fs8.existsSync(file)) {
|
|
17149
17208
|
throw new CLIError(`override file not found: ${file}`, 3 /* NotFound */);
|
|
17150
17209
|
}
|
|
17151
|
-
const content =
|
|
17210
|
+
const content = fs8.readFileSync(file, "utf8");
|
|
17152
17211
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
17153
17212
|
if (bytes > 16 * 1024) {
|
|
17154
17213
|
throw new CLIError(`compile prompt override exceeds 16 KB (${bytes} bytes)`, 4 /* Validation */);
|
|
@@ -17172,7 +17231,7 @@ ${pages.length} pages`));
|
|
|
17172
17231
|
const ctx = getCtx();
|
|
17173
17232
|
const resp = await ctx.api.get("/api/wiki/schema");
|
|
17174
17233
|
if (opts.out) {
|
|
17175
|
-
|
|
17234
|
+
fs8.writeFileSync(opts.out, resp.body);
|
|
17176
17235
|
if (!isJson()) {
|
|
17177
17236
|
console.log(source_default.dim(`wrote ${resp.body.length} bytes to ${opts.out}`));
|
|
17178
17237
|
} else {
|
|
@@ -17188,10 +17247,10 @@ ${pages.length} pages`));
|
|
|
17188
17247
|
});
|
|
17189
17248
|
schemaCmd.command("set").description("Replace the tenant wiki schema with the contents of <file>").requiredOption("--file <file>", "Path to schema markdown file").action(async (opts) => {
|
|
17190
17249
|
const ctx = getCtx();
|
|
17191
|
-
if (!
|
|
17250
|
+
if (!fs8.existsSync(opts.file)) {
|
|
17192
17251
|
throw new CLIError(`schema file not found: ${opts.file}`, 3 /* NotFound */);
|
|
17193
17252
|
}
|
|
17194
|
-
const body =
|
|
17253
|
+
const body = fs8.readFileSync(opts.file, "utf8");
|
|
17195
17254
|
const bytes = Buffer.byteLength(body, "utf8");
|
|
17196
17255
|
if (bytes > 64 * 1024) {
|
|
17197
17256
|
throw new CLIError(`schema body exceeds 64 KB (${bytes} bytes)`, 4 /* Validation */);
|
|
@@ -17229,7 +17288,7 @@ var PRESETS = {
|
|
|
17229
17288
|
cloud: {
|
|
17230
17289
|
name: "cloud",
|
|
17231
17290
|
api_url: "https://martha.nomadriver.co",
|
|
17232
|
-
keycloak_url: "https://
|
|
17291
|
+
keycloak_url: "https://auth.nomadriver.co",
|
|
17233
17292
|
keycloak_realm: "frank"
|
|
17234
17293
|
},
|
|
17235
17294
|
local: {
|
|
@@ -17244,7 +17303,7 @@ async function prompt2(rl, question, fallback) {
|
|
|
17244
17303
|
return answer.trim() || fallback;
|
|
17245
17304
|
}
|
|
17246
17305
|
async function initCommand(opts) {
|
|
17247
|
-
const presetKey = opts.preset ?? "
|
|
17306
|
+
const presetKey = opts.preset ?? "cloud";
|
|
17248
17307
|
const preset = PRESETS[presetKey];
|
|
17249
17308
|
if (!preset) {
|
|
17250
17309
|
throw new CLIError(`Unknown preset: ${presetKey}. Choose one of: ${Object.keys(PRESETS).join(", ")}.`, 4 /* Validation */);
|
|
@@ -17255,7 +17314,7 @@ async function initCommand(opts) {
|
|
|
17255
17314
|
throw new CLIError(`Profile ${source_default.cyan(profileName)} already exists. Re-run with --force to overwrite, or pass --profile <name> to add a new one.`, 5 /* Conflict */);
|
|
17256
17315
|
}
|
|
17257
17316
|
const interactive = !opts.noInteractive && process.stdin.isTTY && !opts.apiUrl && !opts.keycloakUrl && !opts.keycloakRealm;
|
|
17258
|
-
|
|
17317
|
+
let profile = {
|
|
17259
17318
|
api_url: opts.apiUrl ?? preset.api_url,
|
|
17260
17319
|
keycloak_url: opts.keycloakUrl ?? preset.keycloak_url,
|
|
17261
17320
|
keycloak_realm: opts.keycloakRealm ?? preset.keycloak_realm,
|
|
@@ -17287,7 +17346,6 @@ Martha CLI — first-run setup
|
|
|
17287
17346
|
console.log();
|
|
17288
17347
|
console.log(source_default.green(`Profile saved.
|
|
17289
17348
|
`));
|
|
17290
|
-
console.log(` Preset: ${source_default.cyan(preset.name)}`);
|
|
17291
17349
|
console.log(` Profile: ${source_default.cyan(profileName)}`);
|
|
17292
17350
|
console.log(` API URL: ${profile.api_url}`);
|
|
17293
17351
|
console.log(` Keycloak: ${profile.keycloak_url}`);
|
|
@@ -17299,7 +17357,7 @@ Martha CLI — first-run setup
|
|
|
17299
17357
|
console.log(source_default.dim(" martha doctor # verify setup"));
|
|
17300
17358
|
}
|
|
17301
17359
|
function registerInitCommand(program2) {
|
|
17302
|
-
program2.command("init").description("Create or update a profile in ~/.martha/config.yaml").option("--preset <name>", "Preset:
|
|
17360
|
+
program2.command("init").description("Create or update a profile in ~/.martha/config.yaml").option("--preset <name>", "Preset: cloud or local", "cloud").option("--name <name>", "Profile name (defaults to preset name)").option("--force", "Overwrite an existing profile").option("--api-url <url>", "Override the API URL").option("--keycloak-url <url>", "Override the Keycloak URL").option("--keycloak-realm <realm>", "Override the Keycloak realm").option("--no-interactive", "Skip prompts; use defaults / overrides only").action(async (opts) => {
|
|
17303
17361
|
await initCommand(opts);
|
|
17304
17362
|
});
|
|
17305
17363
|
}
|
|
@@ -17547,19 +17605,19 @@ function registerDoctorCommand(program2) {
|
|
|
17547
17605
|
|
|
17548
17606
|
// src/commands/skill.ts
|
|
17549
17607
|
init_errors();
|
|
17550
|
-
import
|
|
17551
|
-
import
|
|
17552
|
-
import { fileURLToPath
|
|
17608
|
+
import fs9 from "node:fs";
|
|
17609
|
+
import path5 from "node:path";
|
|
17610
|
+
import { fileURLToPath } from "node:url";
|
|
17553
17611
|
function locateSkill() {
|
|
17554
|
-
const here =
|
|
17612
|
+
const here = path5.dirname(fileURLToPath(import.meta.url));
|
|
17555
17613
|
const candidates = [
|
|
17556
|
-
|
|
17557
|
-
|
|
17558
|
-
|
|
17559
|
-
|
|
17614
|
+
path5.join(here, "skills", "martha-cli", "SKILL.md"),
|
|
17615
|
+
path5.join(here, "..", "skills", "martha-cli", "SKILL.md"),
|
|
17616
|
+
path5.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
|
|
17617
|
+
path5.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
|
|
17560
17618
|
];
|
|
17561
17619
|
for (const p of candidates) {
|
|
17562
|
-
if (
|
|
17620
|
+
if (fs9.existsSync(p))
|
|
17563
17621
|
return p;
|
|
17564
17622
|
}
|
|
17565
17623
|
return null;
|
|
@@ -17569,7 +17627,7 @@ async function skillCommand() {
|
|
|
17569
17627
|
if (!skillPath) {
|
|
17570
17628
|
throw new CLIError("SKILL.md not found in the installed package. Reinstall via `npm i -g @aiaiai-pt/martha-cli` or `npx -y @aiaiai-pt/martha-cli@latest skill`.", 1 /* Error */);
|
|
17571
17629
|
}
|
|
17572
|
-
const body =
|
|
17630
|
+
const body = fs9.readFileSync(skillPath, "utf-8");
|
|
17573
17631
|
process.stdout.write(body);
|
|
17574
17632
|
}
|
|
17575
17633
|
function registerSkillCommand(program2) {
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ Martha has a few distinct primitives that get confused in everyday talk. This is
|
|
|
15
15
|
|---|---|---|
|
|
16
16
|
| **Tenant** | Opaque data isolation boundary (`tenant_id` string). All queries filter by this. Set from the JWT, never from request body. | Every entity is tenant-scoped. You don't pass it explicitly to the CLI — it comes from your token. |
|
|
17
17
|
| **Client** | A chat-API consumer (web app, SMS sender, voice line). Has `keycloak_client_id`, `system_prompts`, allowlists. **Not a tenant.** | Use when you're configuring how a frontend or messaging channel talks to Martha. |
|
|
18
|
-
| **Agent** | An `AgentDefinition` row:
|
|
18
|
+
| **Agent** | An `AgentDefinition` row: prompt + LLM config + loop config + tool grants. Cloud or external. | Cloud agents are run by Martha (Temporal). External agents are remote harnesses that authenticate and execute Martha tasks. |
|
|
19
19
|
| **Team** | A named group of agents with a routing strategy (`round_robin`, `manual`, `external`). | Use to spread work across many similar agents (e.g. 5 ork instances doing code review). |
|
|
20
20
|
| **Task** | A unit of work with goal, priority, lifecycle (`open`/`claimed`/`running`/`completed`/`failed`/`cancelled`/`stale`/`poisoned`). Optionally linked to a tracker issue. | Use to queue async work for agents. Humans or agents can create them. |
|
|
21
21
|
| **Function** | An HTTP endpoint or platform Python callable that an agent can invoke as a tool. Stored as `FunctionDefinition`. | Define once, grant to many agents. |
|
|
@@ -35,7 +35,7 @@ Everything below operates on these primitives. When in doubt, run `martha status
|
|
|
35
35
|
| Flag | Purpose |
|
|
36
36
|
|---|---|
|
|
37
37
|
| `--json` | Machine-readable JSON output. **Always set this when piping to `jq` or parsing.** Without it, output is human-formatted and may include color codes. |
|
|
38
|
-
| `--profile <name>` | Use a named profile from `~/.martha/
|
|
38
|
+
| `--profile <name>` | Use a named profile from `~/.martha/profiles/`. Default profile is `default`. |
|
|
39
39
|
| `--api-url <url>` | Override `MARTHA_API_URL`. Useful for hitting staging/prod from the same shell. |
|
|
40
40
|
| `--verbose` | DEBUG-level logging on stderr. Shows HTTP requests, retry attempts, token expiry decisions. |
|
|
41
41
|
| `--quiet` | Suppress informational output. Errors still print. |
|
|
@@ -47,28 +47,11 @@ Everything below operates on these primitives. When in doubt, run `martha status
|
|
|
47
47
|
|
|
48
48
|
## Authentication
|
|
49
49
|
|
|
50
|
-
Install does not pre-provision a global profile. Configure one explicitly before
|
|
51
|
-
running commands:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
# Local development stack
|
|
55
|
-
martha init --no-interactive --preset local
|
|
56
|
-
|
|
57
|
-
# Customer, staging, or private-cloud tenant
|
|
58
|
-
martha init --no-interactive --name acme \
|
|
59
|
-
--api-url https://martha.acme.example \
|
|
60
|
-
--keycloak-url https://auth.acme.example \
|
|
61
|
-
--keycloak-realm acme
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Use `martha init --preset cloud` only when you intentionally want the hosted
|
|
65
|
-
Martha cloud profile (`martha.nomadriver.co` + `keycloak.frank.nomadriver.co`).
|
|
66
|
-
|
|
67
50
|
The CLI resolves credentials in this priority order. The first non-empty wins:
|
|
68
51
|
|
|
69
52
|
1. `MARTHA_TOKEN` — raw JWT, bypasses all login flows. Highest priority.
|
|
70
53
|
2. `MARTHA_CLIENT_ID` + `MARTHA_CLIENT_SECRET` — OAuth2 client credentials, auto-refreshes.
|
|
71
|
-
3. Profile-stored token from prior `martha auth login` (cached
|
|
54
|
+
3. Profile-stored token from prior `martha auth login` (cached at `~/.martha/profiles/<name>.json`).
|
|
72
55
|
4. Browser-based OIDC (interactive only, won't fire in non-TTY).
|
|
73
56
|
|
|
74
57
|
```bash
|
|
@@ -147,22 +130,9 @@ agent_type: cloud
|
|
|
147
130
|
auth_method: service_account # Slice 3B: provisions Keycloak SA on create
|
|
148
131
|
system_prompt: "You write friendly morning briefings."
|
|
149
132
|
llm_config: { provider: anthropic, model: claude-sonnet-4-5-20250929 }
|
|
150
|
-
loop_config: { max_iterations: 5 }
|
|
133
|
+
loop_config: { enabled: true, max_iterations: 5 }
|
|
151
134
|
```
|
|
152
135
|
|
|
153
|
-
Agent field notes:
|
|
154
|
-
|
|
155
|
-
- `system_prompt` is the durable instruction for the agent.
|
|
156
|
-
- Grant an agent to a chat client with
|
|
157
|
-
`martha clients grant <client> agent <agent>` to make it callable as a tool
|
|
158
|
-
from that client.
|
|
159
|
-
- `runs_as_chat` is an advanced top-level chat-takeover override, not normal
|
|
160
|
-
provisioning. Use it only when exactly one chat client should replace its own
|
|
161
|
-
`system_prompt`, `llm_config`, and tools with one agent.
|
|
162
|
-
- `loop_config.enabled` is legacy compatibility. New YAML should not use it.
|
|
163
|
-
- Workflow `llm` nodes use `config.prompt` for a one-step task prompt; that is
|
|
164
|
-
separate from an agent `system_prompt`.
|
|
165
|
-
|
|
166
136
|
`--dry-run` prints what would change without writing. `--yes` skips the confirmation when applying to a non-empty tenant.
|
|
167
137
|
|
|
168
138
|
### Export (back up or migrate)
|
|
@@ -271,11 +241,11 @@ Default `execute_on` mapping: `llm` → `local`; `function` / `wait` / `transfor
|
|
|
271
241
|
```bash
|
|
272
242
|
martha agents list [--inactive] [--limit 50]
|
|
273
243
|
martha agents get <name> # Includes auth_method, status, llm_config, granted functions/workflows
|
|
274
|
-
martha agents create --name <n> --model <m> --
|
|
244
|
+
martha agents create --name <n> --model <m> --prompt "system prompt" \
|
|
275
245
|
[--description "..."] [--temperature 0.7] [--max-tokens 4096] \
|
|
276
246
|
[--type cloud|external] [--auth service-account|api-key] \
|
|
277
247
|
[--tags code-review,python] [--local-tools filesystem,git]
|
|
278
|
-
martha agents update <name>
|
|
248
|
+
martha agents update <name> [--model <m>] [--prompt "..."] [--description "..."]
|
|
279
249
|
martha agents delete <name> [--hard] [--yes]
|
|
280
250
|
martha agents versions <name>
|
|
281
251
|
martha agents rollback <name> <version>
|
|
@@ -865,7 +835,7 @@ martha teams add-member refactoring-team ork-1
|
|
|
865
835
|
# Agent side
|
|
866
836
|
export MARTHA_CLIENT_ID=<from above>
|
|
867
837
|
export MARTHA_CLIENT_SECRET=<from above>
|
|
868
|
-
export MARTHA_API_URL=https://martha.
|
|
838
|
+
export MARTHA_API_URL=https://martha.nomadriver.co
|
|
869
839
|
martha auth login --service-account
|
|
870
840
|
martha tasks poll --json # Should return open team-scoped tasks
|
|
871
841
|
```
|