@skrr-ai/cli 0.1.10 → 0.1.12
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/dist/base-command.d.ts +1 -13
- package/dist/base-command.js +78 -1
- package/dist/commands/browser/skill/show.js +7 -1
- package/dist/commands/code/index.d.ts +1 -0
- package/dist/commands/code/index.js +9 -1
- package/dist/commands/commitments/analytics/index.js +2 -0
- package/dist/commands/daemon/byok.d.ts +1 -0
- package/dist/commands/daemon/byok.js +2 -1
- package/dist/commands/daemon/install.d.ts +15 -0
- package/dist/commands/daemon/install.js +39 -2
- package/dist/commands/daemon/login.d.ts +31 -0
- package/dist/commands/daemon/login.js +56 -0
- package/dist/commands/daemon/restart.d.ts +9 -0
- package/dist/commands/daemon/restart.js +36 -0
- package/dist/commands/daemon/start.d.ts +1 -0
- package/dist/commands/daemon/start.js +2 -1
- package/dist/commands/daemon/status.d.ts +1 -0
- package/dist/commands/daemon/status.js +2 -1
- package/dist/commands/daemon/stop.d.ts +1 -0
- package/dist/commands/daemon/stop.js +2 -1
- package/dist/commands/daemon/uninstall.d.ts +1 -0
- package/dist/commands/daemon/uninstall.js +2 -1
- package/dist/commands/daemon/unlock.d.ts +9 -0
- package/dist/commands/daemon/unlock.js +33 -0
- package/dist/commands/goals/key-results/create.js +32 -1
- package/dist/commands/goals/key-results/update.d.ts +11 -0
- package/dist/commands/goals/key-results/update.js +80 -2
- package/dist/commands/goals/plan-now.d.ts +54 -2
- package/dist/commands/goals/plan-now.js +175 -18
- package/dist/commands/goals/planner-config.d.ts +60 -9
- package/dist/commands/goals/planner-config.js +82 -34
- package/dist/commands/goals/revisions.js +17 -0
- package/dist/commands/goals/show.d.ts +17 -0
- package/dist/commands/goals/show.js +90 -3
- package/dist/commands/login.js +53 -0
- package/dist/commands/spaces/create.js +2 -1
- package/dist/commands/spaces/index.js +9 -1
- package/dist/commands/spaces/list.d.ts +18 -0
- package/dist/commands/spaces/list.js +57 -7
- package/dist/commands/spaces/show.js +4 -1
- package/dist/commands/spaces/summary.d.ts +4 -0
- package/dist/commands/spaces/summary.js +77 -1
- package/dist/commands/spaces/update.d.ts +4 -0
- package/dist/commands/spaces/update.js +38 -1
- package/dist/commands/tasks/actionability.js +40 -1
- package/dist/commands/tasks/activity.d.ts +29 -0
- package/dist/commands/tasks/activity.js +47 -0
- package/dist/commands/tasks/complete.d.ts +47 -0
- package/dist/commands/tasks/complete.js +159 -12
- package/dist/commands/tasks/create.d.ts +26 -0
- package/dist/commands/tasks/create.js +60 -1
- package/dist/commands/tasks/events/append.d.ts +2 -0
- package/dist/commands/tasks/events/append.js +41 -10
- package/dist/commands/tasks/events/list.js +60 -9
- package/dist/commands/tasks/output.js +22 -2
- package/dist/commands/tasks/ready.d.ts +38 -0
- package/dist/commands/tasks/ready.js +37 -0
- package/dist/commands/tasks/runs.d.ts +22 -0
- package/dist/commands/tasks/runs.js +110 -2
- package/dist/commands/tasks/show.d.ts +28 -0
- package/dist/commands/tasks/show.js +61 -0
- package/dist/commands/tasks/timeline.d.ts +7 -0
- package/dist/commands/tasks/timeline.js +34 -3
- package/dist/commands/tasks/update.d.ts +20 -0
- package/dist/commands/tasks/update.js +38 -1
- package/dist/commands/whoami.d.ts +40 -0
- package/dist/commands/whoami.js +57 -9
- package/dist/commands/wiki/ls.d.ts +23 -0
- package/dist/commands/wiki/ls.js +63 -10
- package/dist/commands/wiki/mv.d.ts +70 -0
- package/dist/commands/wiki/mv.js +198 -4
- package/dist/commands/wiki/rm.js +12 -1
- package/dist/commands/wiki/write.js +35 -3
- package/dist/help.d.ts +27 -0
- package/dist/help.js +48 -0
- package/dist/hooks/command-not-found.d.ts +31 -0
- package/dist/hooks/command-not-found.js +12 -80
- package/dist/lib/command-miss.d.ts +60 -0
- package/dist/lib/command-miss.js +128 -0
- package/dist/lib/commitment-analytics.d.ts +10 -0
- package/dist/lib/commitment-analytics.js +10 -0
- package/dist/lib/config.js +6 -2
- package/dist/lib/daemon-installer.d.ts +62 -0
- package/dist/lib/daemon-installer.js +247 -0
- package/dist/lib/daemon-setup.d.ts +49 -0
- package/dist/lib/daemon-setup.js +103 -0
- package/dist/lib/daemonBroker.d.ts +7 -1
- package/dist/lib/daemonBroker.js +71 -0
- package/dist/lib/daemonHandoff.d.ts +47 -0
- package/dist/lib/daemonHandoff.js +304 -0
- package/dist/lib/exec-oversky.d.ts +30 -0
- package/dist/lib/exec-oversky.js +41 -0
- package/dist/lib/format.d.ts +7 -0
- package/dist/lib/format.js +27 -5
- package/dist/lib/login.js +22 -2
- package/dist/lib/sky-code.js +1 -1
- package/dist/lib/task-transcript.d.ts +9 -0
- package/dist/lib/task-transcript.js +13 -2
- package/dist/lib/tasks.d.ts +34 -0
- package/dist/lib/tasks.js +70 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/configRoot.d.ts +50 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/configRoot.js +67 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +21 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseKeys.d.ts +87 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseKeys.js +94 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.d.ts +161 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.js +235 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/configRoot.d.ts +50 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/configRoot.js +59 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseKeys.d.ts +87 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseKeys.js +91 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.d.ts +161 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.js +227 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +15 -4
- package/oclif.manifest.json +1831 -1598
- package/package.json +5 -3
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeDoneDisagreement = describeDoneDisagreement;
|
|
4
|
+
exports.describeField = describeField;
|
|
3
5
|
const core_1 = require("@oclif/core");
|
|
4
6
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
7
|
const base_command_1 = require("../../../base-command");
|
|
@@ -35,7 +37,16 @@ class GoalsKeyResultsUpdate extends base_command_1.BaseCommand {
|
|
|
35
37
|
description: 'Clear the description (sends null)',
|
|
36
38
|
exclusive: ['description'],
|
|
37
39
|
}),
|
|
38
|
-
|
|
40
|
+
// The bound is declared, not just described. `--progress 150` and
|
|
41
|
+
// `--progress -5` both came back as a bare `Invalid request data /
|
|
42
|
+
// HTTP_400` from the server — byte-identical for two different mistakes,
|
|
43
|
+
// in a CLI that gives excellent field-level errors for enums. oclif
|
|
44
|
+
// refuses an out-of-range value naming the range, before the round trip.
|
|
45
|
+
progress: core_1.Flags.integer({
|
|
46
|
+
description: 'Progress 0–100 (used in manual mode)',
|
|
47
|
+
min: 0,
|
|
48
|
+
max: 100,
|
|
49
|
+
}),
|
|
39
50
|
'progress-mode': core_1.Flags.string({
|
|
40
51
|
description: 'How progress is tracked',
|
|
41
52
|
options: [...data_provider_1.KEY_RESULT_PROGRESS_MODES],
|
|
@@ -86,7 +97,74 @@ class GoalsKeyResultsUpdate extends base_command_1.BaseCommand {
|
|
|
86
97
|
return;
|
|
87
98
|
}
|
|
88
99
|
const k = updated;
|
|
89
|
-
|
|
100
|
+
// `Updated key result <uuid> on goal <uuid>` echoed two ids the caller had
|
|
101
|
+
// just typed and said nothing about what changed — so a status-only update
|
|
102
|
+
// never confirmed that the value passed is the value that stuck, and the
|
|
103
|
+
// documented next move was to run it again "purely as insurance". Echo the
|
|
104
|
+
// patched fields as the SERVER now holds them, not as they were sent.
|
|
105
|
+
const echo = Object.keys(data)
|
|
106
|
+
.map((field) => `${field}=${describeField(k, field)}`)
|
|
107
|
+
.join(' ');
|
|
108
|
+
this.log(`Updated key result ${k.id ?? args.krId} on goal ${args.goalId}: ${echo}`);
|
|
109
|
+
// The write echoes the number back, so without this the caller has every
|
|
110
|
+
// reason to believe it took — and then `key-results list` shows `-`.
|
|
111
|
+
if (k.progressMode === 'auto' && flags.progress !== undefined) {
|
|
112
|
+
this.warn(`The value is stored. ${AUTO_MODE_NOTE}`);
|
|
113
|
+
}
|
|
114
|
+
// `done` beside `25%` on one row, with nothing reconciling them. The two
|
|
115
|
+
// ARE independent axes — a key result can be closed out incomplete, and
|
|
116
|
+
// pulling progress to 100 here would be the CLI inventing a policy the
|
|
117
|
+
// server does not have, destroying the recorded number in the process. So
|
|
118
|
+
// say which one is which instead, at the moment they are made to disagree,
|
|
119
|
+
// rather than leaving it to be discovered from a list days later.
|
|
120
|
+
const progress = (0, data_provider_1.resolveKeyResultProgress)(k);
|
|
121
|
+
const disagreement = describeDoneDisagreement(flags.status, progress);
|
|
122
|
+
if (disagreement)
|
|
123
|
+
this.warn(disagreement);
|
|
90
124
|
}
|
|
91
125
|
}
|
|
92
126
|
exports.default = GoalsKeyResultsUpdate;
|
|
127
|
+
/**
|
|
128
|
+
* The sentence to print when `done` and a sub-100 progress are made to
|
|
129
|
+
* disagree, or `null` when they do not. Exported for `update.spec.ts`.
|
|
130
|
+
*
|
|
131
|
+
* Returns a warning rather than reconciling the two: pulling progress to 100
|
|
132
|
+
* would be the CLI inventing a policy the server does not have, and it would
|
|
133
|
+
* destroy the recorded number on the way.
|
|
134
|
+
*/
|
|
135
|
+
function describeDoneDisagreement(status, progress) {
|
|
136
|
+
if (status !== 'done' || typeof progress !== 'number' || progress >= 100)
|
|
137
|
+
return null;
|
|
138
|
+
return (`Status is 'done' but progress is ${progress}%. They are independent: status is the verdict, ` +
|
|
139
|
+
`progress is the measure, and a key result may legitimately be closed out incomplete. ` +
|
|
140
|
+
`If you meant it to read as finished, also pass --progress 100.`);
|
|
141
|
+
}
|
|
142
|
+
/** How the server now holds one patched field, for the confirmation line. */
|
|
143
|
+
function describeField(kr, field) {
|
|
144
|
+
const value = kr[field];
|
|
145
|
+
if (value === null)
|
|
146
|
+
return 'cleared';
|
|
147
|
+
if (value === undefined)
|
|
148
|
+
return '(not returned)';
|
|
149
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Why an `auto` key result reads as `-` from the CLI.
|
|
153
|
+
*
|
|
154
|
+
* Auto progress is computed from tasks linked to THIS key result
|
|
155
|
+
* (`Task.keyResultIds`), and the task↔key-result link is a DOCUMENTED
|
|
156
|
+
* omission from the CLI surface — it sits in `domain-api-parity.spec.ts`'s
|
|
157
|
+
* allowlist with a stated reason. So `--progress-mode auto` is offered by a
|
|
158
|
+
* surface that cannot make it move, and `resolveKeyResultProgress` renders
|
|
159
|
+
* `_computedProgress ?? null` for auto, i.e. `-`, while a `--progress` value
|
|
160
|
+
* you set is stored and returned by `update`.
|
|
161
|
+
*
|
|
162
|
+
* The result was one CLI reporting two different values for one field: the
|
|
163
|
+
* write echoed 40 and the list showed `-`. Saying so at the point of use is
|
|
164
|
+
* the honest fix; silently rendering the stored number instead would hide a
|
|
165
|
+
* real distinction (a manual value is not a computed one), and adding the
|
|
166
|
+
* link command would reverse a recorded decision.
|
|
167
|
+
*/
|
|
168
|
+
const AUTO_MODE_NOTE = 'Auto progress is computed from tasks linked to this key result, and linking a task to a key ' +
|
|
169
|
+
'result is deliberately not in the CLI — so this reads as "-" here until it is linked ' +
|
|
170
|
+
'elsewhere. Use --progress-mode manual to track a number from the CLI.';
|
|
@@ -1,4 +1,50 @@
|
|
|
1
|
+
import type { GoalPlanNowResponse } from '@skrr-ai/data-provider';
|
|
1
2
|
import { BaseCommand } from '../../base-command';
|
|
3
|
+
/**
|
|
4
|
+
* Turn the planner's internal `reason` enum into a sentence.
|
|
5
|
+
*
|
|
6
|
+
* These strings are produced by `GoalPlanner` and are not declared anywhere in
|
|
7
|
+
* `@skrr-ai/data-provider`, so this is a rendering table and deliberately NOT a
|
|
8
|
+
* second vocabulary: an unrecognised reason is returned verbatim rather than
|
|
9
|
+
* hidden, which is what keeps a new server reason visible instead of silently
|
|
10
|
+
* dropping to "the planner did something".
|
|
11
|
+
*
|
|
12
|
+
* Dogfooded: `plan-now` printed a bare `note: lock_held`, which reads as "the
|
|
13
|
+
* planner ran and found nothing", when in fact the planner never started.
|
|
14
|
+
*/
|
|
15
|
+
export declare function describePlannerReason(reason: string | null | undefined): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Did the tick fail to run? `processed` is the server's own answer, and it is
|
|
18
|
+
* the field the old renderer ignored while printing three zeros and exiting 0.
|
|
19
|
+
*
|
|
20
|
+
* Only an EXPLICIT `false` counts, on either field. An absent `processed` is
|
|
21
|
+
* treated as "it ran", so a CLI pointed at a server that predates the field
|
|
22
|
+
* keeps working — the old renderer made the same assumption, just without
|
|
23
|
+
* saying so.
|
|
24
|
+
*/
|
|
25
|
+
export declare function plannerTickDidNotRun(result: GoalPlanNowResponse | undefined): boolean;
|
|
26
|
+
/** Did the REQUEST give up, as opposed to the tick failing? */
|
|
27
|
+
export declare function isPlannerRequestTimeout(err: unknown): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* What a gateway timeout on a planner tick actually licenses us to say.
|
|
30
|
+
*
|
|
31
|
+
* The shared error path maps every 502/503/504 to "this is a service outage …
|
|
32
|
+
* Nothing was changed", and for a planner tick both halves were wrong when
|
|
33
|
+
* dogfooded: the very next command against the same host answered instantly,
|
|
34
|
+
* and a retry two seconds later came back `lock_held` — proof the first request
|
|
35
|
+
* had been accepted and was still running. "Nothing was changed" is the
|
|
36
|
+
* dangerous half, because it invites a retry of a non-idempotent, task-creating,
|
|
37
|
+
* billable operation.
|
|
38
|
+
*
|
|
39
|
+
* A 504 is a gateway giving up. It carries no information about whether the
|
|
40
|
+
* origin finished the work, so this says exactly that and names the commands
|
|
41
|
+
* that CAN answer it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function plannerTimeoutMessage(opts: {
|
|
44
|
+
goalId: string;
|
|
45
|
+
status: number;
|
|
46
|
+
bin: string;
|
|
47
|
+
}): string;
|
|
2
48
|
/**
|
|
3
49
|
* `skrr goals plan-now <goal-id>` — run a single planner tick immediately,
|
|
4
50
|
* independent of the configured cadence (or even when no cadence is set). The
|
|
@@ -8,8 +54,12 @@ import { BaseCommand } from '../../base-command';
|
|
|
8
54
|
* response reports what the tick did: tasks created, existing tasks refined, and
|
|
9
55
|
* how many created tasks were also scheduled for auto-execution (only when the
|
|
10
56
|
* goal's `autoExecute` is on and an assignee resolves). `--space` scopes the
|
|
11
|
-
* created tasks to a
|
|
12
|
-
*
|
|
57
|
+
* created tasks to a space and backfills spaceId on the goal's existing tasks.
|
|
58
|
+
*
|
|
59
|
+
* The route answers HTTP 200 with `processed: false` when the tick was SKIPPED
|
|
60
|
+
* (a lock held by a running tick, a cadence that is not due, an archived goal).
|
|
61
|
+
* That is not a success, so this command does not report it as one — see the
|
|
62
|
+
* `processed` branch in `run()`.
|
|
13
63
|
*/
|
|
14
64
|
export default class GoalsPlanNow extends BaseCommand {
|
|
15
65
|
static description: string;
|
|
@@ -22,4 +72,6 @@ export default class GoalsPlanNow extends BaseCommand {
|
|
|
22
72
|
space: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
73
|
};
|
|
24
74
|
run(): Promise<void>;
|
|
75
|
+
/** A gateway timeout on THIS request, reported as what it is. */
|
|
76
|
+
private failIfRequestTimedOut;
|
|
25
77
|
}
|
|
@@ -1,8 +1,119 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describePlannerReason = describePlannerReason;
|
|
4
|
+
exports.plannerTickDidNotRun = plannerTickDidNotRun;
|
|
5
|
+
exports.isPlannerRequestTimeout = isPlannerRequestTimeout;
|
|
6
|
+
exports.plannerTimeoutMessage = plannerTimeoutMessage;
|
|
3
7
|
const core_1 = require("@oclif/core");
|
|
4
8
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
9
|
const base_command_1 = require("../../base-command");
|
|
10
|
+
/**
|
|
11
|
+
* Statuses that mean "this REQUEST gave up", not "the work failed".
|
|
12
|
+
*
|
|
13
|
+
* Same set `agents plan-day` uses, and for the same reason: a planner tick is a
|
|
14
|
+
* live agent turn that routinely outlives the edge's request budget, so the
|
|
15
|
+
* gateway answers while the server keeps running. A 4xx means the tick never
|
|
16
|
+
* started and a 500 means the route itself threw — neither is a timeout, and
|
|
17
|
+
* both are reported by the shared error path.
|
|
18
|
+
*/
|
|
19
|
+
const TIMEOUT_STATUSES = new Set([408, 504, 524]);
|
|
20
|
+
/**
|
|
21
|
+
* Reasons whose no-op is CONTENTION or timing — the same call can be expected
|
|
22
|
+
* to run later. Everything else (`goal_archived`, `goal_planner_disabled`, …)
|
|
23
|
+
* needs the user to change something first, so a script retrying it would spin.
|
|
24
|
+
*/
|
|
25
|
+
const TRANSIENT_SKIP_REASONS = new Set(['lock_held', 'not_due', 'schedule_superseded']);
|
|
26
|
+
/**
|
|
27
|
+
* Turn the planner's internal `reason` enum into a sentence.
|
|
28
|
+
*
|
|
29
|
+
* These strings are produced by `GoalPlanner` and are not declared anywhere in
|
|
30
|
+
* `@skrr-ai/data-provider`, so this is a rendering table and deliberately NOT a
|
|
31
|
+
* second vocabulary: an unrecognised reason is returned verbatim rather than
|
|
32
|
+
* hidden, which is what keeps a new server reason visible instead of silently
|
|
33
|
+
* dropping to "the planner did something".
|
|
34
|
+
*
|
|
35
|
+
* Dogfooded: `plan-now` printed a bare `note: lock_held`, which reads as "the
|
|
36
|
+
* planner ran and found nothing", when in fact the planner never started.
|
|
37
|
+
*/
|
|
38
|
+
function describePlannerReason(reason) {
|
|
39
|
+
if (!reason)
|
|
40
|
+
return null;
|
|
41
|
+
switch (reason) {
|
|
42
|
+
case 'lock_held':
|
|
43
|
+
return 'a planner tick for this goal is already running, so this one did not start';
|
|
44
|
+
case 'not_due':
|
|
45
|
+
return "the goal's next tick is not due yet";
|
|
46
|
+
case 'schedule_superseded':
|
|
47
|
+
return "the goal's cadence changed after this tick was scheduled, so it was dropped";
|
|
48
|
+
case 'goal_deleted':
|
|
49
|
+
return 'the goal no longer exists';
|
|
50
|
+
case 'goal_archived':
|
|
51
|
+
return 'the goal is archived';
|
|
52
|
+
case 'goal_planner_disabled':
|
|
53
|
+
return "the goal's auto-planner has no cadence set";
|
|
54
|
+
case 'missing_goal_id':
|
|
55
|
+
return 'the request carried no goal id';
|
|
56
|
+
case 'at_capacity':
|
|
57
|
+
return 'the goal is already at its maxActiveTasks cap, so no new tasks were created';
|
|
58
|
+
case 'at_capacity_refined':
|
|
59
|
+
return 'the goal is at its maxActiveTasks cap, so existing tasks were refined instead of new ones created';
|
|
60
|
+
case 'coordinator_suppressed':
|
|
61
|
+
return 'another proactive surface had just raised this goal, so no new tasks were created';
|
|
62
|
+
case 'all_creates_failed':
|
|
63
|
+
return 'the planner proposed tasks but none of them could be created';
|
|
64
|
+
case 'no_changes':
|
|
65
|
+
return 'the planner ran and found nothing to add or refine';
|
|
66
|
+
case 'one_voice_busy':
|
|
67
|
+
return 'another agent was already speaking for this owner, so the tick stayed quiet';
|
|
68
|
+
default:
|
|
69
|
+
// `one_voice_<something>` is a family the server composes at run time.
|
|
70
|
+
if (reason.startsWith('one_voice_')) {
|
|
71
|
+
return `the one-voice coordinator suppressed this tick (${reason.slice('one_voice_'.length)})`;
|
|
72
|
+
}
|
|
73
|
+
return reason;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Did the tick fail to run? `processed` is the server's own answer, and it is
|
|
78
|
+
* the field the old renderer ignored while printing three zeros and exiting 0.
|
|
79
|
+
*
|
|
80
|
+
* Only an EXPLICIT `false` counts, on either field. An absent `processed` is
|
|
81
|
+
* treated as "it ran", so a CLI pointed at a server that predates the field
|
|
82
|
+
* keeps working — the old renderer made the same assumption, just without
|
|
83
|
+
* saying so.
|
|
84
|
+
*/
|
|
85
|
+
function plannerTickDidNotRun(result) {
|
|
86
|
+
return result?.ok === false || result?.processed === false;
|
|
87
|
+
}
|
|
88
|
+
/** Did the REQUEST give up, as opposed to the tick failing? */
|
|
89
|
+
function isPlannerRequestTimeout(err) {
|
|
90
|
+
const status = err?.status;
|
|
91
|
+
return typeof status === 'number' && TIMEOUT_STATUSES.has(status);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* What a gateway timeout on a planner tick actually licenses us to say.
|
|
95
|
+
*
|
|
96
|
+
* The shared error path maps every 502/503/504 to "this is a service outage …
|
|
97
|
+
* Nothing was changed", and for a planner tick both halves were wrong when
|
|
98
|
+
* dogfooded: the very next command against the same host answered instantly,
|
|
99
|
+
* and a retry two seconds later came back `lock_held` — proof the first request
|
|
100
|
+
* had been accepted and was still running. "Nothing was changed" is the
|
|
101
|
+
* dangerous half, because it invites a retry of a non-idempotent, task-creating,
|
|
102
|
+
* billable operation.
|
|
103
|
+
*
|
|
104
|
+
* A 504 is a gateway giving up. It carries no information about whether the
|
|
105
|
+
* origin finished the work, so this says exactly that and names the commands
|
|
106
|
+
* that CAN answer it.
|
|
107
|
+
*/
|
|
108
|
+
function plannerTimeoutMessage(opts) {
|
|
109
|
+
const { goalId, status, bin } = opts;
|
|
110
|
+
return (`The planner tick for goal ${goalId} outlived this request (HTTP ${status}). ` +
|
|
111
|
+
'That is the gateway giving up, not a verdict on the tick: the run may still be ' +
|
|
112
|
+
'executing and may still create tasks. Nothing here observed whether it finished. ' +
|
|
113
|
+
`Check with \`${bin} goals plan-now ${goalId}\` — a "lock_held" answer means it is still ` +
|
|
114
|
+
`running — or \`${bin} goals tasks ${goalId}\` to see what it produced. ` +
|
|
115
|
+
'Do not retry blindly: a second tick is a second billable agent run.');
|
|
116
|
+
}
|
|
6
117
|
/**
|
|
7
118
|
* `skrr goals plan-now <goal-id>` — run a single planner tick immediately,
|
|
8
119
|
* independent of the configured cadence (or even when no cadence is set). The
|
|
@@ -12,11 +123,15 @@ const base_command_1 = require("../../base-command");
|
|
|
12
123
|
* response reports what the tick did: tasks created, existing tasks refined, and
|
|
13
124
|
* how many created tasks were also scheduled for auto-execution (only when the
|
|
14
125
|
* goal's `autoExecute` is on and an assignee resolves). `--space` scopes the
|
|
15
|
-
* created tasks to a
|
|
16
|
-
*
|
|
126
|
+
* created tasks to a space and backfills spaceId on the goal's existing tasks.
|
|
127
|
+
*
|
|
128
|
+
* The route answers HTTP 200 with `processed: false` when the tick was SKIPPED
|
|
129
|
+
* (a lock held by a running tick, a cadence that is not due, an archived goal).
|
|
130
|
+
* That is not a success, so this command does not report it as one — see the
|
|
131
|
+
* `processed` branch in `run()`.
|
|
17
132
|
*/
|
|
18
133
|
class GoalsPlanNow extends base_command_1.BaseCommand {
|
|
19
|
-
static description = 'Run one auto-planner tick for a goal right now';
|
|
134
|
+
static description = 'Run one auto-planner tick for a goal right now (a live agent turn that creates and refines tasks)';
|
|
20
135
|
static examples = [
|
|
21
136
|
'<%= config.bin %> goals plan-now <goal-id>',
|
|
22
137
|
'<%= config.bin %> goals plan-now <goal-id> --space <space-id>',
|
|
@@ -28,41 +143,83 @@ class GoalsPlanNow extends base_command_1.BaseCommand {
|
|
|
28
143
|
static flags = {
|
|
29
144
|
json: core_1.Flags.boolean({ description: 'Output the planner result as JSON' }),
|
|
30
145
|
space: core_1.Flags.string({
|
|
31
|
-
description:
|
|
146
|
+
description: "Scope created tasks to this space (also backfills spaceId on the goal's existing tasks)",
|
|
32
147
|
}),
|
|
33
148
|
};
|
|
34
149
|
async run() {
|
|
35
150
|
this.requireAuth();
|
|
36
151
|
const { args, flags } = await this.parse(GoalsPlanNow);
|
|
152
|
+
// A tick is an LLM run; minutes of silence is normal and used to be
|
|
153
|
+
// indistinguishable from a hung command. stderr, so `--json` stdout stays a
|
|
154
|
+
// single parseable document.
|
|
155
|
+
this.logToStderr(`Running one planner tick for goal ${args.id}. This is a live agent turn and can take minutes.`);
|
|
37
156
|
let result;
|
|
38
157
|
try {
|
|
39
158
|
result = await data_provider_1.dataService.planGoalNow(args.id, flags.space ? { spaceId: flags.space } : {});
|
|
40
159
|
}
|
|
41
160
|
catch (err) {
|
|
161
|
+
this.failIfRequestTimedOut(err, args.id);
|
|
42
162
|
this.handleApiError(err);
|
|
43
163
|
}
|
|
44
|
-
if (flags.json) {
|
|
45
|
-
this.log(JSON.stringify(result, null, 2));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
164
|
const r = result;
|
|
49
|
-
|
|
50
|
-
|
|
165
|
+
const reasonText = describePlannerReason(r?.reason);
|
|
166
|
+
if (plannerTickDidNotRun(r)) {
|
|
167
|
+
// The backfill runs BEFORE planning and unconditionally, so a skipped
|
|
168
|
+
// tick can still have moved rows. Saying "nothing happened" would be the
|
|
169
|
+
// same class of lie this whole command is being fixed for.
|
|
170
|
+
const backfilled = r?.backfilledCount
|
|
171
|
+
? ` ${r.backfilledCount} existing task(s) were still backfilled to the given space.`
|
|
172
|
+
: '';
|
|
173
|
+
const transient = TRANSIENT_SKIP_REASONS.has(r?.reason ?? '');
|
|
174
|
+
const retry = transient
|
|
175
|
+
? ` Re-run \`${this.config.bin} goals plan-now ${args.id}\` to try again.`
|
|
176
|
+
: '';
|
|
177
|
+
this.failWithCliError({
|
|
178
|
+
message: `The planner did not run for goal ${args.id}` +
|
|
179
|
+
(reasonText ? `: ${reasonText}.` : '.') +
|
|
180
|
+
backfilled +
|
|
181
|
+
retry,
|
|
182
|
+
code: 'PLANNER_TICK_NOT_RUN',
|
|
183
|
+
exit: 1,
|
|
184
|
+
retryable: transient,
|
|
185
|
+
details: r,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (flags.json) {
|
|
189
|
+
this.log(JSON.stringify(r, null, 2));
|
|
51
190
|
return;
|
|
52
191
|
}
|
|
53
192
|
this.log(`Planner tick for goal ${args.id}:`);
|
|
54
|
-
this.log(` created: ${r
|
|
55
|
-
this.log(` refined: ${r
|
|
56
|
-
this.log(` scheduled: ${r
|
|
57
|
-
if (r
|
|
58
|
-
this.log(` backfilled: ${r
|
|
193
|
+
this.log(` created: ${r?.createdCount ?? 0}`);
|
|
194
|
+
this.log(` refined: ${r?.refinedCount ?? 0}`);
|
|
195
|
+
this.log(` scheduled: ${r?.scheduledCount ?? 0}`);
|
|
196
|
+
if (flags.space || r?.backfilledCount) {
|
|
197
|
+
this.log(` backfilled: ${r?.backfilledCount ?? 0}`);
|
|
59
198
|
}
|
|
60
|
-
if (r
|
|
199
|
+
if (r?.resolvedSpaceId) {
|
|
61
200
|
this.log(` space: ${r.resolvedSpaceId}`);
|
|
62
201
|
}
|
|
63
|
-
if (
|
|
64
|
-
|
|
202
|
+
if (reasonText) {
|
|
203
|
+
// The raw enum stays in parentheses: it is what the server logs and what
|
|
204
|
+
// an existing script may already grep for.
|
|
205
|
+
this.log(` note: ${reasonText} (${r?.reason}).`);
|
|
65
206
|
}
|
|
66
207
|
}
|
|
208
|
+
/** A gateway timeout on THIS request, reported as what it is. */
|
|
209
|
+
failIfRequestTimedOut(err, goalId) {
|
|
210
|
+
if (!isPlannerRequestTimeout(err))
|
|
211
|
+
return;
|
|
212
|
+
const status = err.status;
|
|
213
|
+
this.failWithCliError({
|
|
214
|
+
message: plannerTimeoutMessage({ goalId, status, bin: this.config.bin }),
|
|
215
|
+
code: 'PLANNER_TICK_TIMED_OUT',
|
|
216
|
+
status,
|
|
217
|
+
exit: 5,
|
|
218
|
+
// Deliberately false. The request may be repeated by a HUMAN who has
|
|
219
|
+
// checked, but `plan-now` is not idempotent, so a script must not treat
|
|
220
|
+
// this as "safe to send again".
|
|
221
|
+
retryable: false,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
67
224
|
}
|
|
68
225
|
exports.default = GoalsPlanNow;
|
|
@@ -1,16 +1,67 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* The goal's auto-planner state, as the CLI is willing to state it.
|
|
4
|
+
*
|
|
5
|
+
* Two fields are DERIVED rather than passed through, and both exist because the
|
|
6
|
+
* stored document can disagree with itself:
|
|
7
|
+
*
|
|
8
|
+
* - `enabled` reads `tickIntervalMs`, the one field the server's own disable
|
|
9
|
+
* path clears (`$unset tickIntervalMs` + `$unset nextTickAt`). It is the
|
|
10
|
+
* single source of "is this armed".
|
|
11
|
+
* - `nextTickAt` is forced to null whenever the planner is off. The stored
|
|
12
|
+
* timestamp can SURVIVE a disable: `GoalPlanner.planForGoal` writes
|
|
13
|
+
* `nextTickAt` unconditionally at claim time and again at completion, so a
|
|
14
|
+
* tick that was already in flight when `--disable` ran re-creates the field
|
|
15
|
+
* afterwards. Dogfooded — `goals show --json` reported a `nextTickAt` in the
|
|
16
|
+
* past on a disabled planner while the plain view said `interval: disabled`,
|
|
17
|
+
* i.e. the two renderings of one command disagreed about whether a tick was
|
|
18
|
+
* scheduled. A disabled planner has no next tick; saying so is the only
|
|
19
|
+
* reading that is true from either side.
|
|
20
|
+
*
|
|
21
|
+
* The raw server fields are never rewritten — `goals show --json` still carries
|
|
22
|
+
* `tickIntervalMs` / `nextTickAt` verbatim, and this view sits beside them.
|
|
23
|
+
*/
|
|
24
|
+
export type GoalPlannerView = {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
tickIntervalMs: number | null;
|
|
27
|
+
maxActiveTasks: number | null;
|
|
28
|
+
autoExecute: boolean | null;
|
|
29
|
+
nextTickAt: string | null;
|
|
30
|
+
lastTickedAt: string | null;
|
|
31
|
+
/**
|
|
32
|
+
* `tickLockedAt` — set when a tick claims the goal, unset when it finishes or
|
|
33
|
+
* fails. A value here means a claimed tick has not released the lock: it is
|
|
34
|
+
* either still running or it crashed. We report the timestamp and decline to
|
|
35
|
+
* choose between those two, because the CLI cannot tell them apart.
|
|
36
|
+
*/
|
|
37
|
+
tickLockedAt: string | null;
|
|
38
|
+
};
|
|
39
|
+
/** Read the planner state off a Goal document (lean or typed). */
|
|
40
|
+
export declare function goalPlannerView(goal: Record<string, unknown> | undefined | null): GoalPlannerView;
|
|
41
|
+
/**
|
|
42
|
+
* The plain-text planner block, shared by `planner-config` and `goals show` so
|
|
43
|
+
* the two commands cannot drift into describing the same state differently.
|
|
44
|
+
*/
|
|
45
|
+
export declare function renderGoalPlannerLines(view: GoalPlannerView): string[];
|
|
46
|
+
/**
|
|
47
|
+
* `skrr goals planner-config <goal-id>` — read, configure, or disable the
|
|
48
|
+
* goal's auto-planner: the scheduled tick that runs a planner agent to create
|
|
49
|
+
* and refine tasks toward the goal.
|
|
50
|
+
*
|
|
51
|
+
* With no flags this READS the config. It used to refuse, which made the only
|
|
52
|
+
* way to answer "is the planner on for this goal?" a write — and for `--disable`
|
|
53
|
+
* that meant turning the automation off to find out it was on. A recurring,
|
|
54
|
+
* task-creating, optionally agent-starting automation must be readable without
|
|
55
|
+
* changing it.
|
|
6
56
|
*
|
|
7
57
|
* Wraps `PATCH /api/goals/:goalId/planner-config` (data-provider
|
|
8
|
-
* `updateGoalPlannerConfig`)
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
58
|
+
* `updateGoalPlannerConfig`) for writes and `GET /api/goals/:goalId` for the
|
|
59
|
+
* read. Setting a cadence enables the planner; `--disable` (which sends
|
|
60
|
+
* `tickIntervalMs: null`) turns it off. The interval flags mirror the
|
|
61
|
+
* trigger-create convention elsewhere in this CLI: `--interval-minutes` for the
|
|
62
|
+
* ergonomic path, `--interval-ms` for the raw value. The server owns the bounds
|
|
63
|
+
* (interval 60s–30d, maxActiveTasks 1–50) and rejects out-of-range values,
|
|
64
|
+
* surfaced here via the standard error path.
|
|
14
65
|
*/
|
|
15
66
|
export default class GoalsPlannerConfig extends BaseCommand {
|
|
16
67
|
static description: string;
|
|
@@ -1,24 +1,78 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.goalPlannerView = goalPlannerView;
|
|
4
|
+
exports.renderGoalPlannerLines = renderGoalPlannerLines;
|
|
3
5
|
const core_1 = require("@oclif/core");
|
|
4
6
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
7
|
const base_command_1 = require("../../base-command");
|
|
8
|
+
function str(value) {
|
|
9
|
+
return typeof value === 'string' && value ? value : null;
|
|
10
|
+
}
|
|
11
|
+
/** Read the planner state off a Goal document (lean or typed). */
|
|
12
|
+
function goalPlannerView(goal) {
|
|
13
|
+
const g = (goal ?? {});
|
|
14
|
+
const tickIntervalMs = typeof g.tickIntervalMs === 'number' ? g.tickIntervalMs : null;
|
|
15
|
+
const enabled = tickIntervalMs != null;
|
|
16
|
+
return {
|
|
17
|
+
enabled,
|
|
18
|
+
tickIntervalMs,
|
|
19
|
+
maxActiveTasks: typeof g.maxActiveTasks === 'number' ? g.maxActiveTasks : null,
|
|
20
|
+
autoExecute: typeof g.autoExecute === 'boolean' ? g.autoExecute : null,
|
|
21
|
+
nextTickAt: enabled ? str(g.nextTickAt) : null,
|
|
22
|
+
lastTickedAt: str(g.lastTickedAt),
|
|
23
|
+
tickLockedAt: str(g.tickLockedAt),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
6
26
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
27
|
+
* The plain-text planner block, shared by `planner-config` and `goals show` so
|
|
28
|
+
* the two commands cannot drift into describing the same state differently.
|
|
29
|
+
*/
|
|
30
|
+
function renderGoalPlannerLines(view) {
|
|
31
|
+
const interval = view.enabled
|
|
32
|
+
? `${Math.round(view.tickIntervalMs / 60_000)}m (${view.tickIntervalMs}ms)`
|
|
33
|
+
: 'disabled';
|
|
34
|
+
let autoExecute = '-';
|
|
35
|
+
if (view.autoExecute !== null) {
|
|
36
|
+
autoExecute = view.autoExecute ? 'on' : 'off';
|
|
37
|
+
}
|
|
38
|
+
const lines = [
|
|
39
|
+
` interval: ${interval}`,
|
|
40
|
+
` maxActiveTasks: ${view.maxActiveTasks ?? '-'}`,
|
|
41
|
+
` autoExecute: ${autoExecute}`,
|
|
42
|
+
];
|
|
43
|
+
if (view.nextTickAt)
|
|
44
|
+
lines.push(` nextTickAt: ${view.nextTickAt}`);
|
|
45
|
+
if (view.lastTickedAt)
|
|
46
|
+
lines.push(` lastTickedAt: ${view.lastTickedAt}`);
|
|
47
|
+
if (view.tickLockedAt) {
|
|
48
|
+
lines.push(` tickLockedAt: ${view.tickLockedAt} (a claimed tick has not released this lock)`);
|
|
49
|
+
}
|
|
50
|
+
return lines;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* `skrr goals planner-config <goal-id>` — read, configure, or disable the
|
|
54
|
+
* goal's auto-planner: the scheduled tick that runs a planner agent to create
|
|
55
|
+
* and refine tasks toward the goal.
|
|
56
|
+
*
|
|
57
|
+
* With no flags this READS the config. It used to refuse, which made the only
|
|
58
|
+
* way to answer "is the planner on for this goal?" a write — and for `--disable`
|
|
59
|
+
* that meant turning the automation off to find out it was on. A recurring,
|
|
60
|
+
* task-creating, optionally agent-starting automation must be readable without
|
|
61
|
+
* changing it.
|
|
10
62
|
*
|
|
11
63
|
* Wraps `PATCH /api/goals/:goalId/planner-config` (data-provider
|
|
12
|
-
* `updateGoalPlannerConfig`)
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
64
|
+
* `updateGoalPlannerConfig`) for writes and `GET /api/goals/:goalId` for the
|
|
65
|
+
* read. Setting a cadence enables the planner; `--disable` (which sends
|
|
66
|
+
* `tickIntervalMs: null`) turns it off. The interval flags mirror the
|
|
67
|
+
* trigger-create convention elsewhere in this CLI: `--interval-minutes` for the
|
|
68
|
+
* ergonomic path, `--interval-ms` for the raw value. The server owns the bounds
|
|
69
|
+
* (interval 60s–30d, maxActiveTasks 1–50) and rejects out-of-range values,
|
|
70
|
+
* surfaced here via the standard error path.
|
|
18
71
|
*/
|
|
19
72
|
class GoalsPlannerConfig extends base_command_1.BaseCommand {
|
|
20
|
-
static description = "
|
|
73
|
+
static description = "Read, configure, or disable a goal's auto-planner (a recurring agent tick that CREATES and refines tasks under the goal)";
|
|
21
74
|
static examples = [
|
|
75
|
+
'<%= config.bin %> goals planner-config <goal-id>',
|
|
22
76
|
'<%= config.bin %> goals planner-config <goal-id> --interval-minutes 60',
|
|
23
77
|
'<%= config.bin %> goals planner-config <goal-id> --interval-minutes 60 --max-active-tasks 5 --auto-execute',
|
|
24
78
|
'<%= config.bin %> goals planner-config <goal-id> --no-auto-execute',
|
|
@@ -28,24 +82,24 @@ class GoalsPlannerConfig extends base_command_1.BaseCommand {
|
|
|
28
82
|
id: core_1.Args.string({ description: 'Goal ID', required: true, ignoreStdin: true }),
|
|
29
83
|
};
|
|
30
84
|
static flags = {
|
|
31
|
-
json: core_1.Flags.boolean({ description: 'Output the
|
|
85
|
+
json: core_1.Flags.boolean({ description: 'Output the goal as JSON' }),
|
|
32
86
|
'interval-minutes': core_1.Flags.integer({
|
|
33
|
-
description: '
|
|
87
|
+
description: 'How often the planner runs, in minutes. Setting it arms the planner: each tick creates and refines tasks under this goal',
|
|
34
88
|
exclusive: ['interval-ms', 'disable'],
|
|
35
89
|
}),
|
|
36
90
|
'interval-ms': core_1.Flags.integer({
|
|
37
|
-
description: '
|
|
91
|
+
description: 'Same cadence in milliseconds (raw value; arms the planner)',
|
|
38
92
|
exclusive: ['interval-minutes', 'disable'],
|
|
39
93
|
}),
|
|
40
94
|
disable: core_1.Flags.boolean({
|
|
41
|
-
description: '
|
|
95
|
+
description: 'Disarm the auto-planner — clears the cadence so no further ticks run',
|
|
42
96
|
exclusive: ['interval-minutes', 'interval-ms'],
|
|
43
97
|
}),
|
|
44
98
|
'max-active-tasks': core_1.Flags.integer({
|
|
45
|
-
description: '
|
|
99
|
+
description: 'Stop creating new tasks once this many are active under the goal (server bounds: 1–50)',
|
|
46
100
|
}),
|
|
47
101
|
'auto-execute': core_1.Flags.boolean({
|
|
48
|
-
description: '
|
|
102
|
+
description: 'Also START the agent on each planner-created task that has an assignee, without asking (use --no-auto-execute to turn off)',
|
|
49
103
|
allowNo: true,
|
|
50
104
|
}),
|
|
51
105
|
};
|
|
@@ -68,34 +122,28 @@ class GoalsPlannerConfig extends base_command_1.BaseCommand {
|
|
|
68
122
|
if (flags['auto-execute'] !== undefined) {
|
|
69
123
|
data.autoExecute = flags['auto-execute'];
|
|
70
124
|
}
|
|
71
|
-
|
|
72
|
-
this.error('Nothing to update. Pass --interval-minutes/--interval-ms, --disable, --max-active-tasks, or --auto-execute/--no-auto-execute.', { exit: 1 });
|
|
73
|
-
}
|
|
125
|
+
const isWrite = Object.keys(data).length > 0;
|
|
74
126
|
let result;
|
|
75
127
|
try {
|
|
76
|
-
result =
|
|
128
|
+
result = isWrite
|
|
129
|
+
? await data_provider_1.dataService.updateGoalPlannerConfig({ goalId: args.id, data })
|
|
130
|
+
: await data_provider_1.dataService.getGoal(args.id);
|
|
77
131
|
}
|
|
78
132
|
catch (err) {
|
|
79
133
|
this.handleApiError(err);
|
|
80
134
|
}
|
|
81
135
|
if (flags.json) {
|
|
82
|
-
this.log(JSON.stringify(result, null, 2));
|
|
136
|
+
this.log(JSON.stringify({ ...result, planner: goalPlannerView(result) }, null, 2));
|
|
83
137
|
return;
|
|
84
138
|
}
|
|
85
|
-
const g = result;
|
|
86
|
-
const interval = g.tickIntervalMs == null
|
|
87
|
-
? 'disabled'
|
|
88
|
-
: `${Math.round(g.tickIntervalMs / 60_000)}m (${g.tickIntervalMs}ms)`;
|
|
89
|
-
let autoExecute = '-';
|
|
90
|
-
if (g.autoExecute !== undefined) {
|
|
91
|
-
autoExecute = g.autoExecute ? 'on' : 'off';
|
|
92
|
-
}
|
|
93
139
|
this.log(`Planner config for goal ${args.id}:`);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
this.log(
|
|
140
|
+
for (const line of renderGoalPlannerLines(goalPlannerView(result))) {
|
|
141
|
+
this.log(line);
|
|
142
|
+
}
|
|
143
|
+
if (!isWrite) {
|
|
144
|
+
this.log('');
|
|
145
|
+
this.log(`Change it with \`${this.config.bin} goals planner-config ${args.id} --interval-minutes <n>\`, ` +
|
|
146
|
+
'or `--disable` to turn it off.');
|
|
99
147
|
}
|
|
100
148
|
}
|
|
101
149
|
}
|